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
|
@@ -15,7 +15,8 @@ Current implementation notes:
|
|
|
15
15
|
- Cursor status uses one coordinated `ctx.ui.setStatus("cursor", ...)` value for fast, non-default plan mode, and the local-only `http1` transport marker; the default pi footer remains intact.
|
|
16
16
|
- Installed `@cursor/sdk` user messages accept images, and Cursor models are treated as image-capable; registered input metadata is `text` plus `image`.
|
|
17
17
|
- Image payload forwarding sends images only from the latest user message. If the latest user turn is plain text after an earlier image turn, the transcript keeps an `[image omitted from transcript]` placeholder but no image bytes are sent to Cursor. The prompt explicitly tells Cursor that prior image bytes are unavailable and to ask the user to reattach or describe a prior image when needed. Carrying images forward across turns remains a future product decision because it affects token cost, privacy, stale visual context, and expected multimodal follow-up behavior.
|
|
18
|
-
- Exact `@cursor/sdk@1.0.
|
|
18
|
+
- Exact `@cursor/sdk@1.0.27` is a package dependency of this extension; users should not need a global SDK install. Pi 0.84.0 is the minimum supported and current validation baseline, while optional published Pi core peer dependencies use `"*"` ranges per current Pi package guidance.
|
|
19
|
+
- After each finished SDK run, the provider calls `agent.getUsage()` (no `runId` on local agents; `runId` only for cloud `run-*` IDs) and maps billed spend into pi `usage` spend fields. Occupancy `totalTokens` uses only in-window local turn-ended occupancy below the latest compaction `tokensBefore`; billed rows never become occupancy, so footer/auto-compact stay aligned with the post-compact Cursor prompt.
|
|
19
20
|
- Startup discovery does not duplicate Pi CLI parsing: it uses stored `~/.pi/agent/auth.json` API-key auth from `/login`, then `CURSOR_API_KEY`, otherwise it registers the bundled fallback catalog. Provider turns keep Pi's resolved `options.apiKey`. `/cursor-refresh-models` and `/cursor-cloud` mutations resolve provider `cursor` through the command context's Pi ModelRegistry, then normalize placeholders through `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults and the user-level local HTTP transport preference.
|
|
20
21
|
- Cursor Cloud repository overrides accept only HTTPS repository URLs without userinfo, query parameters, or fragments. Invalid values fail during preflight before `Agent.create()`, messages never echo the supplied URL, and shared provider/maintainer scrubbing removes URL/SCP-style userinfo defensively.
|
|
21
22
|
- Cursor Cloud requires a persisted pi session. Immediately after remote `Agent.create()` returns, before debug work or abort checks, the provider appends a branch-local pi lifecycle entry, fsyncs the existing Pi session JSONL anchor through a read-write descriptor, and then fsyncs a newline-framed sidecar keyed by the stable pi session ID (POSIX mode `0600`; Windows inherits the user session directory ACL) in the session directory. Journal creation is exclusive, and existing append/read opens reject symlinks and require matching regular-file descriptor/path identity before using the descriptor. Existing session files use the exact lifecycle entry ID as anchor; fileless first turns use an orphan marker that a same-session-ID restart durably claims onto exactly one matching or replacement branch, surviving the timestamped path change and later JSONL creation without granting sibling access; returned run IDs are fsynced before abort/wait handling, readers skip malformed/truncated lines independently, and branch-only history events are reduced after deduplicated sidecar history, and tombstones are tracked before records exist. A durable sidecar result remains authoritative if its optional Pi mirror append fails; if the sidecar result itself fails, the prior durable intent remains pending and blocks retry rather than claiming success. `--no-session` and durable-ledger failures fail closed before send, while post-send ledger failure requests bounded cancellation. `/cursor-cloud` always validates the embedded session ID, accepts only null anchors while truly fileless, and reconciles each orphan to one branch before listing or mutation. Successive record events merge run/branch metadata monotonically even when mirrored sources arrive out of order. Archive/delete require resolved Cursor auth, fsync a durable intent before the SDK call, and fsync a durable success result afterward; unresolved intent blocks repeat mutation and requires dashboard inspection.
|
|
@@ -28,19 +29,19 @@ Current implementation notes:
|
|
|
28
29
|
- Prompt text is the primary provider/bridge contract. Bootstrap prompts carry a short boundary block plus the callable-surface manifest by default (`PI_CURSOR_TOOL_MANIFEST=1`). MCP `listTools` descriptions use a one-line pointer to the bootstrap prompt instead of repeating the full contract (`buildCursorPiBridgeMcpToolDescription()`). Cursor must call the exposed `pi__*` MCP name, not the real pi tool name shown in pi history or transcripts. When exposed, `pi__mcp` takes preference over Cursor-configured MCP for MCP work and `pi__subagent` takes preference over Cursor-native subagents for delegation; the Cursor-native surfaces remain fallbacks when the matching pi bridge tool is absent or unavailable. Pi emits and executes the real pi tool name. Maintainer debug: `/cursor-tools` prints bridge/manifest enablement, effective `PI_CURSOR_SETTING_SOURCES`, and the current callable-surface snapshot.
|
|
29
30
|
- The provider also registers `cursor_ask_question` for Cursor models when the bridge and default-on `PI_CURSOR_ASK_QUESTION` control are enabled. While the tool awaits pi UI input it emits package event `pi-cursor-sdk:ask-question:blocked` with `{ active: true }` and clears `{ active: false }` in `finally`; the tool runs with `executionMode: "sequential"` so parallel sibling calls cannot overlap dialogs. Cursor sees it as `pi__cursor_ask_question`, and pi executes it through the normal tool path so interactive users can choose options from pi UI. `PI_CURSOR_ASK_QUESTION=0` removes only this tool while preserving the rest of the bridge. In non-UI modes it reports that UI is unavailable so Cursor can state a default assumption instead. When pi has visible Agent Skills loaded, the provider rewrites the skill catalog for Cursor and registers `cursor_activate_skill` as `pi__cursor_activate_skill`; pi executes it through the normal tool path so Cursor can load the full `SKILL.md` and skill resource list for the current pi-loaded skill source of truth. `PI_CURSOR_PI_TOOL_BRIDGE=0` disables the local bridge, including question and skill activation bridging. Cloud Cursor agents remain out of scope for the bridge.
|
|
30
31
|
- The bridge queues MCP calls, emits provider `toolcall_*` events, waits for matching pi `toolResult` messages by `toolCallId`, resolves the result back into the same live Cursor SDK run without creating a new `Agent`, and never calls tool `execute()` handlers directly. The same-run resume invariant holds unless the run was disposed, aborted, or cancelled.
|
|
31
|
-
- Cursor SDK MCP tool calls use a guarded timeout override because installed `@cursor/sdk` 1.0.
|
|
32
|
-
- Cursor SDK local safety controls are off by default. `--cursor-auto-review` / `PI_CURSOR_AUTO_REVIEW` and `--cursor-sandbox` / `PI_CURSOR_SANDBOX` pass only explicit enabled values into `Agent.create({ local })`; user or trusted project config can set `local.autoReview` and `local.sandboxOptions.enabled`; project config is active only when Pi's project-trust flow reached the extension and approved the project or the run used explicit `--approve`, and project saves require the same immutable trust provenance rather than creating Pi trust resources automatically. Pi 0.
|
|
32
|
+
- Cursor SDK MCP tool calls use a guarded timeout override because installed `@cursor/sdk` 1.0.27 still has a 60-second MCP request default with no public per-server timeout option. The extension extends the verified Cursor SDK MCP `callTool` timeout path to 3600 seconds by default and shortens the verified first-send MCP initialize/listTools timeout paths to 10 seconds by default so unavailable configured MCP servers do not block the first reply for a full minute; unknown MCP protocol timeout stacks keep the SDK default. Users can override tool-call timeouts with `PI_CURSOR_MCP_TOOL_TIMEOUT_MS` or `PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS`, and initialize/listTools timeouts with `PI_CURSOR_MCP_CONNECT_TIMEOUT_MS` or `PI_CURSOR_MCP_CONNECT_TIMEOUT_SECONDS`. Bridged `CallTool` waits also have a local fail-closed deadline that defaults to and cannot exceed the effective MCP tool timeout; `PI_CURSOR_PI_BRIDGE_CALL_TIMEOUT_MS` can lower it, expiry or MCP cancellation aborts active pi execution when available, and expired bridge events are dropped before pi tool emission.
|
|
33
|
+
- Cursor SDK local safety controls are off by default. `--cursor-auto-review` / `PI_CURSOR_AUTO_REVIEW` and `--cursor-sandbox` / `PI_CURSOR_SANDBOX` pass only explicit enabled values into `Agent.create({ local })`; user or trusted project config can set `local.autoReview` and `local.sandboxOptions.enabled`; project config is active only when Pi's project-trust flow reached the extension and approved the project or the run used explicit `--approve`, and project saves require the same immutable trust provenance rather than creating Pi trust resources automatically. Pi 0.84.0 loads `pi install -l` project-local extensions after the trust event, so those installs require `--approve` on every run that reads or writes `.pi/cursor-sdk.json`. Explicit runtime, fast-default, and HTTP transport saves preserve unrecognized config fields, reject malformed or non-object JSON without rewriting it, and use one lock-protected read-modify-write path; fast saves mutate only the selected model key. Because Pi can mutate its in-memory session branch before a journal append throws, a completed global save is authoritative and the command reports the partial journal failure instead of attempting an ambiguous rollback; the new global value stays authoritative over stale branch entries until a later successful save or session restart.
|
|
33
34
|
- Local HTTP/1.1/SSE compatibility is strictly opt-in through `PI_CURSOR_HTTP_1_1`, `/cursor-http on|off|toggle`, or user `cursor-sdk.json` `local.useHttp1ForAgent`. Precedence is session, environment, user, then the built-in unset default; project config is excluded. Unset makes no `Cursor.configure()` call. Explicit values configure the installed SDK before local `Agent.create()`, extension-owned explicit state is cleared with the SDK's documented `null` reset when returning to unset and during session shutdown before module reload, and default/HTTP2/HTTP1 choices split pooled local agents. Pi's supported CLI/TUI/print/RPC lifecycle has one active session runtime per process; concurrent independent `AgentSession` embedding in one process is outside this transport toggle's contract because the installed SDK setting and executor cache are module-global. The footer adds `http1` only for enabled local runtime; cloud creation and status remain untouched.
|
|
34
35
|
- Bridge diagnostics are opt-in only: `PI_CURSOR_PI_TOOL_BRIDGE_DEBUG=1` writes typed, allowlisted, scrubbed single-line JSONL records to `process.stderr` with prefix `[pi-cursor-sdk:bridge]`. Diagnostics are scrubbed operational logs, not anonymous telemetry. They intentionally include tool names, safe correlation IDs, run lifecycle, exposed pi↔MCP name pairs, queued requests, result resolution, rejection, cancellation, and pending counts. Correlation IDs are generated independently from the tokenized endpoint path, and Cursor MCP call IDs are hashed before serialization. Diagnostics must not include endpoint paths/URLs/path components/tokens, API keys, bearer tokens, cookies, session credentials, raw args/results, stdout/stderr payloads, file contents, Cursor settings output, or local private session paths in tracked docs, and they must not call pi UI status, notification, or footer APIs. If tool names themselves are unacceptable for a release target, bridge debug diagnostics are not safe for shared logs under the current contract.
|
|
35
36
|
- This repo does not provide a generic desktop-automation, browser-driver, or CDP recipe. Provider docs should describe pi-cursor-sdk's Cursor provider/bridge contract only.
|
|
36
|
-
- Cursor internal tool activity is recorded from SDK events and scrubbed. Maintainer reference for `@cursor/sdk@1.0.
|
|
37
|
+
- Cursor internal tool activity is recorded from SDK events and scrubbed. Maintainer reference for `@cursor/sdk@1.0.27` `ToolType` values, runtime alias normalization, and intentional mapping/fallback rules: [Cursor native tool replay — SDK ToolType replay matrix](./cursor-native-tool-replay.md#sdk-tooltype-replay-matrix) (official SDK docs: https://cursor.com/docs/sdk/typescript). In TUI sessions and structured JSON/RPC modes, supported completed `read`, `bash`, `grep`, `find`, `ls`, `edit`, `write`, diagnostics, delete, todo/plan, task, image generation, MCP, semantic search, and screen recording activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so users and JSON/RPC consumers can see native-looking cards/events without rerunning Cursor's reads/shell commands/file edits. Cursor `glob` activity is replayed through native `find` cards. Cursor write activity is replayed through native-looking `write` cards, and Cursor StrReplace/edit activity uses native-looking `edit` only when recorded arguments truthfully satisfy pi's `edit` schema; path-only Cursor edit and notebook edit replay falls back to neutral Cursor activity before pi validation. Diagnostics, delete, todos/plans, task/subagent, image, and MCP activity use neutral Cursor activity cards with pi's default success/error shell. Cursor SDK `task` activity is labeled **Cursor subagent** by default because it represents Cursor-spawned child-agent work; the card summary includes description plus subagent kind/model/short ID when Cursor reports them, and `PI_CURSOR_TASK_PRESENTATION=task` restores the older **Cursor task** wording for comparison. This is visibility over Cursor SDK task events, not a native pi subagent session: pi shows start/final output plus any `conversationSteps` tool-call summaries Cursor returns, but cannot show a live nested read/shell/MCP trail when the SDK only returns final subagent text. Neutral Cursor activity calls include `activityTitle` and, when available, `activitySummary` so partial/collapsed cards preserve identity such as `Cursor plan`, `Cursor todos`, `Cursor subagent`, `Cursor MCP`, or `Cursor edit`. For long-running or externally meaningful Cursor tools (`task`, `shell`, `mcp`, `generateImage`, `recordScreen`, `semSearch`, web search/fetch, plan/todo), the provider may surface one low-noise deferred in-progress thinking line such as `Cursor MCP: external_search` from bounded, scrubbed SDK args; fast local tools (`read`, `grep`, `glob`, and similar) skip lifecycle lines when completion follows immediately, and pi bridge MCP calls are excluded because pi already shows real pi tool execution ([lifecycle visibility](./cursor-native-tool-replay.md#low-noise-tool-lifecycle-visibility)). Replay-only tools display recorded Cursor results, normalize workspace-local paths/diff headers for display, use pi diff colors for edit previews and path-inferred syntax highlighting for write previews, and fail closed if called without a recorded result. Native replay wrappers are registered only for tool names not already owned by another extension; conflicting tools use the bounded scrubbed transcript fallback. Cursor workflow tools such as mode/task/todo/plan activity are not pi workflow controls; reported todo/plan events are displayed as Cursor activity only. Plan/todo replay cards can be followed by Cursor's final plan text, selected from `run.wait().result` when Cursor provides one and trimmed against already-emitted text. Started Cursor SDK tool calls that never receive a completion event are surfaced with bounded user-visible labels/traces (neutral activity cards when native replay routing allows, otherwise the same inactive or transcript trace fallbacks used for completed replay) instead of being silently discarded when the run failed/aborted, produced no assistant text, or involved external/side-effectful tools; incomplete fast local discovery starts (`read`, `grep`, `glob`, `ls`) remain maintainer-debug-only after successful text-producing runs so stale SDK start events do not create red post-answer cards. Explicit failures remain visible when Cursor reports them through completed tool calls or step results. Pi bridge MCP starts remain excluded from duplicate incomplete Cursor cards because pi already shows real pi tool execution. `PI_CURSOR_NATIVE_TOOL_DISPLAY=0` disables native replay, and `PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is a registration-only opt-out that keeps the transcript fallback without shadowing pi tool names. When bridge or native replay cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK activity arrives: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later tool batches as further `toolUse` turns, then Cursor's final assistant answer. For shell replay, completed `stdout` / `stderr` are primary; unambiguous `shell-output-delta` data is also shown as bounded live progress while one shell call is active and used as display-only fallback for empty successful shell completions, while overlapping shell calls drop ambiguous deltas instead of guessing. Print mode keeps bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when no live-run turn split is active.
|
|
37
38
|
- Cursor native replay uses one neutral replay tool name, `cursor`, plus native-compatible card names when renderer-compatible (`read`, `bash`, `grep`, `find`, `ls`, `edit`, `write`). Neutral replay identity lives in `activityTitle`, `activitySummary`, and typed replay details, not in extra registered tool names. Bridge MCP names such as `pi__sem_reindex` are MCP-only; pi session output uses real pi tool names.
|
|
38
|
-
- Local Cursor SDK usage events are used when the SDK reports them before the corresponding pi turn is emitted and the reported counts fit the selected pi model window. For each safe local SDK-attributed assistant turn, `usage.input`, `usage.output`, `usage.cacheRead`, and `usage.cacheWrite` come from the latest per-turn raw `turn-ended.usage` (not SDK `toTokenUsage`): observed local runtime keeps `inputTokens` as the full prompt with cache fields as a partition, even though published SDK `TokenUsage.totalTokens` sums all four fields. Pi maps that raw local shape to disjoint components (`input = inputTokens - cacheReadTokens - cacheWriteTokens`) and `usage.totalTokens = inputTokens + outputTokens` for occupancy/compaction
|
|
39
|
+
- Local Cursor SDK usage events are used when the SDK reports them before the corresponding pi turn is emitted and the reported counts fit the selected pi model window. For each safe local SDK-attributed assistant turn, `usage.input`, `usage.output`, `usage.cacheRead`, and `usage.cacheWrite` come from the latest per-turn raw `turn-ended.usage` (not SDK `toTokenUsage`): observed local runtime keeps `inputTokens` as the full prompt with cache fields as a partition, even though published SDK `TokenUsage.totalTokens` sums all four fields. Pi maps that raw local shape to disjoint components (`input = inputTokens - cacheReadTokens - cacheWriteTokens`) and `usage.totalTokens = inputTokens + outputTokens` for occupancy/compaction only when that total is below the latest compaction `tokensBefore`; otherwise occupancy uses the post-compaction estimate floor. Cloud billed `getUsage()` rows map to spend fields only and never become occupancy. Approximate fallback never reports less occupancy than the last compatible same-model in-window assistant measurement in context. Cumulative `RunResult.usage` is never used for per-message occupancy. If the local SDK reports no usage in time, or reports full-agent-context-sized usage outside the selected model window, the provider falls back to local `input/output` activity estimates while setting `usage.totalTokens` to the current replayable context estimate so footer/compaction context does not collapse after split tool turns; after a split live-run turn times out waiting for SDK usage, later SDK usage for that live run is ignored rather than risk applying stale usage to the wrong pi turn. Cursor SDK cost is unavailable, so cost remains absent/zero. `src/cursor-usage-accounting.ts` owns this policy.
|
|
39
40
|
- Audit observation, 2026-05-19, superseded by the 2026-05-21 replay pass and #68 incomplete visibility, then narrowed by the 2026-05-26 fast-local suppression: a missing-file read with Composer 2.5 emitted `tool-call-started` for Cursor `read`, then streamed final text `Error: File not found`, but did not emit `tool-call-completed` or an `onStep` `toolCall` error result. Leftover external/side-effectful started calls are surfaced at run completion through the same native replay routing as completed tools (activity cards when allowed, otherwise inactive/transcript traces), while fast local discovery starts are debug-only after a successful text-producing run. Cursor-reported completed/step errors remain visible.
|
|
40
41
|
- Maintainer visual verification for replay-card changes should follow [Cursor Native Tool Visual Audit Workflow](./cursor-native-tool-visual-audit.md): offscreen PTY-driven pi run, xterm.js/Playwright screenshot rendering, and JSONL inspection before accepting commits or PRs.
|
|
41
|
-
- Cursor provider/runtime releases must pass the local [Platform Smoke Gate](./platform-smoke.md): `npm run smoke:platform:all`. Cloud-runtime changes must also pass `npm run smoke:cloud`. Use [Cursor Live Smoke Checklist](./cursor-live-smoke-checklist.md) only for focused inner-loop/debug runs with real `pi --approve -e . --cursor-no-fast --model cursor/
|
|
42
|
+
- Cursor provider/runtime releases must pass the local [Platform Smoke Gate](./platform-smoke.md): `npm run smoke:platform:all`. Cloud-runtime changes must also pass `npm run smoke:cloud`. Use [Cursor Live Smoke Checklist](./cursor-live-smoke-checklist.md) only for focused inner-loop/debug runs with real `pi --approve -e . --cursor-no-fast --model cursor/grok-4.6` invocations, manual observation, temporary session dirs, diagnostics scans, and persisted JSONL inspection. See [Cursor testing lessons](./cursor-testing-lessons.md) for auth.json seeding, isolated smoke harnesses, and replay JSONL scans.
|
|
42
43
|
- For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup.
|
|
43
|
-
- Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.
|
|
44
|
+
- Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.27 documentation says the SDK may enable Max Mode automatically when a selected model requires it, but the public local-agent `ModelSelection` path still does not expose a manual Max Mode selector. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends.
|
|
44
45
|
- The installed `@cursor/sdk` exposes latest-style `ModelListItem.aliases`. The extension registers only unambiguous aliases as pi model IDs (with the same context suffixes when applicable) and sends the alias back in `ModelSelection.id`. Cursor-only fast preferences are keyed by the selected SDK model ID/alias, with read fallback for older preferences keyed by the underlying catalog `id`. Aliases shared by multiple base models, such as generic family aliases, are skipped because the pi row metadata would otherwise imply one base model while Cursor may resolve the alias to another.
|
|
45
46
|
- Local restart resume treats user entries already present at `session_start` or selected by tree navigation as crash-ambiguous: an older SDK handle cannot span them because the prior process may already have submitted that prompt. A user entry appended after startup in the current process may span the last completed handle for the normal next send.
|
|
46
47
|
- Persisted pi sessions use a session-scoped Cursor SDK SQLite store at `<getDefaultSdkStateRoot(cwd)>/pi-sessions/<session-hash>/`; create/resume, transcript reads, checkpoint lookup, and exact-ID cleanup all receive that same store. Fileless acquisitions use unique OS-temporary stores that are removed on graceful disposal; invalidation starts a fresh agent instead of reopening a disposed temporary store. Resume entries version the store identity. Legacy entries still resume against the SDK default workspace store, then move to the per-session store after fallback or agent replacement. Removing a persisted pi session does not automatically remove its store directory; only a verified session-derived `pi-sessions/<session-hash>` root may be removed after no pi process uses it. The shared SDK default workspace root recorded by legacy entries must never be removed as session cleanup. Cloud agents are unchanged.
|
|
@@ -134,7 +135,9 @@ CURSOR_API_KEY="your-key" npm run refresh:cursor-snapshots -- --write \
|
|
|
134
135
|
--context-windows ~/.pi/agent/cursor-sdk-context-windows.json
|
|
135
136
|
```
|
|
136
137
|
|
|
137
|
-
Both modes call `Cursor.models.list({ apiKey })` and use the same sanitizer and stable sort. `--check` byte-compares `src/cursor-fallback-models.generated.ts` without writing; `--write` refreshes it and updates `src/bundled-context-windows.ts` only when `--context-windows` is provided. Generated provenance and command output record the installed `@cursor/sdk` version and model count. The script prints model IDs/counts only and scrubs known auth material from SDK errors; it must not print or store API keys. Review generated diffs before committing because Cursor can change aliases, defaults, and parameter meanings.
|
|
138
|
+
Both modes call `Cursor.models.list({ apiKey })` and use the same sanitizer and stable sort. `--check` byte-compares `src/cursor-fallback-models.generated.ts` without writing; `--write` refreshes it and updates `src/bundled-context-windows.ts` only when `--context-windows` is provided. Context-window inputs are limited to current selectable model IDs; redundant default `:fast`/`:slow` aliases collapse to one key, conflicting equivalent selections fail generation, and stale or ambiguous aliases are omitted. Generated provenance and command output record the installed `@cursor/sdk` version and model count. The script prints model IDs/counts only and scrubs known auth material from SDK errors; it must not print or store API keys. Review generated diffs before committing because Cursor can change aliases, defaults, and parameter meanings.
|
|
139
|
+
|
|
140
|
+
Dated evidence for Cursor's assistant-visible, model-specific system text and reconstructed tool guidance lives in [Cursor System Prompts and Tool Guidance — 2026-08-02](https://github.com/fitchmultz/pi-cursor-sdk/blob/main/docs/evidence/cursor-system-prompts-2026-08-02/README.md). Keep that evidence separate from pi-cursor-sdk's own bootstrap prompt: Cursor persists its base system message in the local SDK checkpoint, while this extension sends Pi context and bridge instructions as user content.
|
|
138
141
|
|
|
139
142
|
## Design Direction
|
|
140
143
|
|
|
@@ -181,6 +184,9 @@ cursor/claude-opus-4-8@300k
|
|
|
181
184
|
cursor/composer-2-5
|
|
182
185
|
cursor/composer-2-5:fast
|
|
183
186
|
cursor/composer-2-5:slow
|
|
187
|
+
cursor/grok-4.6
|
|
188
|
+
cursor/grok-4.6:fast
|
|
189
|
+
cursor/grok-4.6:slow
|
|
184
190
|
cursor/gpt-5.5@1m:fast
|
|
185
191
|
```
|
|
186
192
|
|
|
@@ -500,6 +506,7 @@ Use Cursor default variants:
|
|
|
500
506
|
```text
|
|
501
507
|
gpt-5.5 -> cursor/gpt-5.5@1m, thinking medium, fast=false
|
|
502
508
|
composer-2.5 -> cursor/composer-2-5, fast=true
|
|
509
|
+
grok-4.6 -> cursor/grok-4.6, fast=true
|
|
503
510
|
```
|
|
504
511
|
|
|
505
512
|
### Resume Session
|
|
@@ -696,6 +703,20 @@ Fast toggle maps to the Cursor `fast` parameter.
|
|
|
696
703
|
|
|
697
704
|
`shift+tab` maps the available low, medium, and high levels to Cursor `effort`; levels without a catalog value do not invent one.
|
|
698
705
|
|
|
706
|
+
### `grok-4.6`
|
|
707
|
+
|
|
708
|
+
Supports `effort=low|medium|high|xhigh` and `fast=false|true`; it does not advertise context variants. The Cursor default variant is `effort=high` and `fast=true`.
|
|
709
|
+
|
|
710
|
+
```text
|
|
711
|
+
cursor/grok-4.6
|
|
712
|
+
cursor/grok-4.6:fast
|
|
713
|
+
cursor/grok-4.6:slow
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
Fast toggle maps to the Cursor `fast` parameter. `--cursor-no-fast` and `:slow` send `fast=false`.
|
|
717
|
+
|
|
718
|
+
`shift+tab` maps the available low, medium, high, and xhigh levels to Cursor `effort`; levels without a catalog value do not invent one.
|
|
719
|
+
|
|
699
720
|
## Validation Plan
|
|
700
721
|
|
|
701
722
|
Before calling done:
|
|
@@ -28,15 +28,15 @@ The bridge is enabled by default when bridgeable active pi tools exist. Cursor s
|
|
|
28
28
|
Rollback, timeout, and diagnostics controls:
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
PI_CURSOR_ASK_QUESTION=0 pi --model cursor/
|
|
32
|
-
PI_CURSOR_PI_TOOL_BRIDGE=0 pi --model cursor/
|
|
33
|
-
PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1 pi --model cursor/
|
|
34
|
-
PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS=7200 pi --model cursor/
|
|
35
|
-
PI_CURSOR_MCP_TOOL_TIMEOUT_MS=7200000 pi --model cursor/
|
|
36
|
-
PI_CURSOR_PI_BRIDGE_CALL_TIMEOUT_MS=120000 pi --model cursor/
|
|
37
|
-
PI_CURSOR_MCP_CONNECT_TIMEOUT_SECONDS=5 pi --model cursor/
|
|
38
|
-
PI_CURSOR_MCP_CONNECT_TIMEOUT_MS=5000 pi --model cursor/
|
|
39
|
-
PI_CURSOR_PI_TOOL_BRIDGE_DEBUG=1 pi --model cursor/
|
|
31
|
+
PI_CURSOR_ASK_QUESTION=0 pi --model cursor/grok-4.6
|
|
32
|
+
PI_CURSOR_PI_TOOL_BRIDGE=0 pi --model cursor/grok-4.6
|
|
33
|
+
PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1 pi --model cursor/grok-4.6
|
|
34
|
+
PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS=7200 pi --model cursor/grok-4.6
|
|
35
|
+
PI_CURSOR_MCP_TOOL_TIMEOUT_MS=7200000 pi --model cursor/grok-4.6
|
|
36
|
+
PI_CURSOR_PI_BRIDGE_CALL_TIMEOUT_MS=120000 pi --model cursor/grok-4.6
|
|
37
|
+
PI_CURSOR_MCP_CONNECT_TIMEOUT_SECONDS=5 pi --model cursor/grok-4.6
|
|
38
|
+
PI_CURSOR_MCP_CONNECT_TIMEOUT_MS=5000 pi --model cursor/grok-4.6
|
|
39
|
+
PI_CURSOR_PI_TOOL_BRIDGE_DEBUG=1 pi --model cursor/grok-4.6
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
`PI_CURSOR_ASK_QUESTION=0` disables only `cursor_ask_question` / `pi__cursor_ask_question`, leaving the rest of the pi bridge available; it is enabled by default. `PI_CURSOR_PI_TOOL_BRIDGE=0` disables the bridge, including `pi__cursor_ask_question`. `PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1` opts in to exposing overlapping pi tool names that Cursor already has native equivalents for (`read`, `bash`, `write`, `edit`, `grep`, `find`, and `ls`). By default those names are hidden even when pi's Cursor replay wrapper has registered them as extension tools; non-overlapping active built-ins remain bridgeable by default. The installed Cursor SDK uses a 60-second MCP protocol default; pi-cursor-sdk overrides that seam by default with 3600 seconds for MCP `callTool` requests and 10 seconds for verified initialize/listTools requests on first send. Bridged calls also have a local fail-closed deadline capped by the effective MCP tool timeout; lower it with `PI_CURSOR_PI_BRIDGE_CALL_TIMEOUT_MS` to reject stale pending state and abort active pi execution sooner. Unknown MCP protocol timeout stacks keep the SDK default. `PI_CURSOR_PI_TOOL_BRIDGE_DEBUG=1` emits typed, allowlisted, scrubbed single-line JSONL bridge diagnostics to `process.stderr` with prefix `[pi-cursor-sdk:bridge]`; it is off by default, uses run-safe IDs that are not reused in endpoint paths, and does not print endpoint URLs/path components/tokens, raw args/results, file contents, or secrets. Cursor-native tools, Cursor settings, plugins, and configured Cursor MCP servers still come from the Cursor SDK local agent path. Cloud Cursor agents are out of scope for this bridge.
|
|
@@ -64,13 +64,13 @@ When Cursor reports completed tool activity, the extension can display recorded
|
|
|
64
64
|
|
|
65
65
|
Cursor `glob` activity is displayed through native `find` cards.
|
|
66
66
|
|
|
67
|
-
For the full `@cursor/sdk@1.0.
|
|
67
|
+
For the full `@cursor/sdk@1.0.27` `ToolType` set, disposition matrix, and runtime alias normalization, see [SDK ToolType replay matrix](#sdk-tooltype-replay-matrix) below. Official SDK reference: https://cursor.com/docs/sdk/typescript
|
|
68
68
|
|
|
69
69
|
Edit and write activity replays through pi-facing `edit` and `write` cards only when replay arguments truthfully satisfy the matching pi schema, but still uses recorded Cursor results only. The adapter passes through truthful Cursor paths, content when Cursor reported it, and recorded diff/details; it does not pretend Cursor's editing schema is pi's schema and it fails closed if a recorded replay result is missing. Cursor `StrReplace` with recorded replacement text displays as native-looking `edit`; path-only Cursor `edit` and notebook edit activity fall back to neutral Cursor activity so pi does not reject the replay before recorded-result handling. Cursor `write` displays as native-looking `write`. Diagnostics, delete, todos/plans, task/subagent, image, MCP, semantic search, screen recording, and web search/fetch activity use neutral Cursor activity cards with pi's default success/error tool shell. Cursor SDK `task` activity is labeled **Cursor subagent** by default because it represents Cursor-spawned child-agent work; `PI_CURSOR_TASK_PRESENTATION=task` keeps the older **Cursor task** wording for comparison. MCP completions whose `toolName` is `WebSearch` / `web_search` / `WebFetch` / similar are labeled **Cursor web search** or **Cursor web fetch** instead of generic **Cursor MCP**. Neutral Cursor activity cards carry display metadata such as `activityTitle` and `activitySummary`, so partial/collapsed cards can say `Cursor plan`, `Cursor todos`, `Cursor subagent`, `Cursor MCP`, `Cursor semantic search`, `Cursor screen recording`, `Cursor web search`, `Cursor web fetch`, or `Cursor edit` instead of only `Cursor activity`. These replay tools only display recorded Cursor results; they never mutate files or execute tool work directly. Replay paths are normalized to workspace-relative paths when possible. Most collapsed replay cards include bounded previews for diffs and text details so small edits, todos, task output, and MCP results are visible without expanding; web search/fetch activity stays summary-only while collapsed because those cards often arrive after final text and can otherwise bury the answer. Ctrl+O expansion shows the recorded details. Edit previews omit raw unified diff headers and show compact numbered changed/context lines using pi's native diff added/removed/context colors, and write previews use syntax highlighting when pi can infer a language from the path. Image generation replay cards show the saved image path in the collapsed summary and render the image inline when pi terminal image display is enabled and the generated file is still readable.
|
|
70
70
|
|
|
71
71
|
## SDK ToolType replay matrix
|
|
72
72
|
|
|
73
|
-
Source of truth for SDK tool names: `@cursor/sdk@1.0.
|
|
73
|
+
Source of truth for SDK tool names: `@cursor/sdk@1.0.27` conversation `ToolType` values and https://cursor.com/docs/sdk/typescript
|
|
74
74
|
|
|
75
75
|
Implementation owners: `src/cursor-tool-presentation-registry.ts` (canonical names, labels, visibility, replay policy, bridge exclusions for internal replay wrappers, alias normalization, and display-spec key completeness), `src/cursor-transcript-tool-specs.ts` (registry-keyed display implementations for transcript formatting and pi display builders), `src/cursor-native-tool-display-replay.ts` (replay card rendering derived from registry replay metadata), and `src/cursor-web-tool-activity.ts` (MCP/web alias remapping before display lookup).
|
|
76
76
|
|
|
@@ -160,7 +160,7 @@ These are integration boundaries, not pi replay bugs:
|
|
|
160
160
|
- **Live web-search ordering:** local Cursor WebSearch can be absent from live `onDelta`, `onStep`, and `run.stream()` tool events. When the only evidence is a post-run local transcript `webSearchToolCall`, pi can display the **Cursor web search** card only after `run.wait()` finishes. The extension intentionally keeps assistant text streaming instead of buffering the whole answer just to reorder that card.
|
|
161
161
|
- **WebFetch availability:** `pi-cursor-sdk` can display a Cursor web fetch only after the SDK reports a `webFetchToolCall`, web-fetch-shaped MCP completion, or web-fetch host alias. It cannot make the Cursor SDK expose or execute WebFetch in a run where Cursor's tool set does not include it.
|
|
162
162
|
- **Future SDK tools:** Cursor's official SDK docs say tool names, args, and result payloads can change. Unknown completed tools therefore fall back to neutral Cursor activity cards with bounded, scrubbed text. The extension cannot render tools that the SDK never emits.
|
|
163
|
-
- **Process-level transport exceptions:** Connect/network suppression remains scoped to active provider turns; raw Cursor SDK `AbortError` DOMExceptions are suppressed while any provider turn or session process-error guard is active. The exact SDK-provenance `WriteIterableClosedError: WritableIterable is closed` is guarded for the Pi session lifecycle because SDK 1.0.23 controlled-exec can reject after its originating provider turn when it writes an error frame after output closes. The observed raw `write EPIPE` (SDK 1.0.23 local shell executor child stdin write with no stream error listener; stack exactly the single async `WriteWrap.onWriteComplete` frame, no SDK provenance) is guarded only while a local Cursor provider turn is active, and a contained closed pipe marks that turn's pooled/resumable agent transport dead for bounded disposal and recreation on the next acquire. Idle pooled agents do not suppress EPIPE, and multi-frame synchronous write-path EPIPE (piped stdout, dead terminal) stays fatal. Unrelated failures remain fatal; a materially different future SDK process error must be added only after observation.
|
|
163
|
+
- **Process-level transport exceptions:** Connect/network suppression remains scoped to active provider turns; raw Cursor SDK `AbortError` DOMExceptions are suppressed while any provider turn or session process-error guard is active. The exact SDK-provenance `WriteIterableClosedError: WritableIterable is closed` is guarded for the Pi session lifecycle because SDK 1.0.23 controlled-exec can reject after its originating provider turn when it writes an error frame after output closes. The observed raw `write EPIPE` (SDK 1.0.23 local shell executor child stdin write with no stream error listener; 1.0.27 attaches a no-op listener before that write; stack exactly the single async `WriteWrap.onWriteComplete` frame, no SDK provenance) is guarded only while a local Cursor provider turn is active, and a contained closed pipe marks that turn's pooled/resumable agent transport dead for bounded disposal and recreation on the next acquire. Idle pooled agents do not suppress EPIPE, and multi-frame synchronous write-path EPIPE (piped stdout, dead terminal) stays fatal. Unrelated failures remain fatal; a materially different future SDK process error must be added only after observation.
|
|
164
164
|
|
|
165
165
|
Maintainer debug (`PI_CURSOR_SDK_EVENT_DEBUG=1`) still records the same discarded started-call events in `coordinator-events.jsonl` under phase `discarded-incomplete-started-tool-call` for investigation (**#52**), including fast local starts suppressed from successful text-producing runs. User-visible incomplete cards and debug artifacts are complementary: cards explain actionable gaps in the TUI; debug files retain normalized tool names and scrubbed call-id hashes without changing default stderr behavior.
|
|
166
166
|
|
|
@@ -203,7 +203,7 @@ Native replay wrappers are registered only for tool names not already owned by a
|
|
|
203
203
|
Disable native replay registration entirely:
|
|
204
204
|
|
|
205
205
|
```bash
|
|
206
|
-
PI_CURSOR_NATIVE_TOOL_DISPLAY=0 pi --model cursor/
|
|
206
|
+
PI_CURSOR_NATIVE_TOOL_DISPLAY=0 pi --model cursor/grok-4.6
|
|
207
207
|
```
|
|
208
208
|
|
|
209
209
|
`PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is also accepted as a registration-only opt-out.
|
|
@@ -6,7 +6,7 @@ This workflow is the canonical repo path for verifying Cursor SDK tool replay th
|
|
|
6
6
|
|
|
7
7
|
Use it before accepting replay-card commits or PRs, and for every Cursor provider/runtime release where TUI card/color behavior could regress. Text logs and JSONL are necessary, but they are not enough when the claim is visual parity: always keep PNGs for the exact prompt, and keep before/after PNGs when reviewing a rendering change.
|
|
8
8
|
|
|
9
|
-
Current validation baseline:
|
|
9
|
+
Current validation baseline: Pi 0.84.0 or later, exact `@cursor/sdk@1.0.27`, and local validation packages `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` at exact 0.84.0. Optional published Pi core peer dependencies use `"*"` ranges per current Pi package guidance.
|
|
10
10
|
|
|
11
11
|
## Cursor SDK 1.0.17 / pi 0.79.0 cutover visual record
|
|
12
12
|
|
|
@@ -82,7 +82,7 @@ npx playwright install chromium
|
|
|
82
82
|
|
|
83
83
|
`scripts/visual-tui-smoke.mjs` is the durable source of truth for this workflow. It must keep supporting:
|
|
84
84
|
|
|
85
|
-
- fixed-size tmux PTY execution of the parent-resolved `pi --approve -e <extension-dir> --model cursor/
|
|
85
|
+
- fixed-size tmux PTY execution of the parent-resolved `pi --approve -e <extension-dir> --model cursor/grok-4.6`
|
|
86
86
|
- parent-resolved `pi` and `tmux` command paths reused in tmux-launched runs, with `process.execPath`'s directory prepended for prereq checks and tmux launches so Node shims use the validated Node
|
|
87
87
|
- `PI_CURSOR_NATIVE_TOOL_DISPLAY=1`
|
|
88
88
|
- `PI_CURSOR_REGISTER_NATIVE_TOOLS=1` by default
|
|
@@ -90,8 +90,11 @@ npx playwright install chromium
|
|
|
90
90
|
- `PI_CURSOR_PI_TOOL_BRIDGE=0` by default
|
|
91
91
|
- `PI_CURSOR_EXPOSE_BUILTIN_TOOLS=0` by default
|
|
92
92
|
- Cursor SDK event-debug artifact env cleared before each run; `--event-debug` sets a deterministic debug directory under `--out-dir`
|
|
93
|
+
- `PI_CODING_AGENT_DIR` isolated to `<out-dir>/pi-agent`, seeded with the host `auth.json` (0600) plus `quietStartup`/telemetry-off settings, so host extensions, skills, themes, and MCP config cannot leak into captures
|
|
94
|
+
- `PI_OFFLINE=1` and `PI_SKIP_VERSION_CHECK=1`, so update banners and package-update notices cannot appear in captures
|
|
93
95
|
- `TERM=xterm-256color`
|
|
94
|
-
- cwd set to the target audit repo
|
|
96
|
+
- cwd set to the target audit repo; the tmux session starts in `--cwd` with a non-login shell so a stale tmux-server cwd cannot print `getcwd` errors
|
|
97
|
+
- `--session-id` forwarded to pi only when explicitly provided, so fresh captures avoid the new-session warning line
|
|
95
98
|
- prompt paste plus carriage return into the interactive TUI
|
|
96
99
|
- bounded post-prompt wait via `--wait-ms`
|
|
97
100
|
- artifacts outside the repo by default
|
|
@@ -190,7 +190,7 @@ Simulate plan-mode execute stripping with the repo fixture:
|
|
|
190
190
|
It sets active tools to `read`, `bash`, `edit`, `write` on each `turn_start`. Run pi with:
|
|
191
191
|
|
|
192
192
|
```bash
|
|
193
|
-
pi --approve -e scripts/fixtures/plan-strip-shim --cursor-no-fast --model cursor/
|
|
193
|
+
pi --approve -e scripts/fixtures/plan-strip-shim --cursor-no-fast --model cursor/grok-4.6 \
|
|
194
194
|
--session-dir "$SMOKE_DIR/plan-strip" \
|
|
195
195
|
-p 'After reset, read README.md and answer PLAN_STRIP_OK=yes.'
|
|
196
196
|
```
|
|
@@ -257,7 +257,7 @@ The script writes timestamped artifacts under `--out` (default `/tmp/pi-cursor-s
|
|
|
257
257
|
|
|
258
258
|
Stdout prints artifact paths and summary counts only. Raw payloads stay on disk and may contain local paths, project text, tool args/results, or secrets — do not commit or share them.
|
|
259
259
|
|
|
260
|
-
Hard repo rule: Cursor SDK behavior claims must come from the installed `@cursor/sdk` package and/or https://cursor.com/docs/sdk/typescript, not from memory or ad-hoc probes alone. Current cutover validation targets exact `@cursor/sdk@1.0.
|
|
260
|
+
Hard repo rule: Cursor SDK behavior claims must come from the installed `@cursor/sdk` package and/or https://cursor.com/docs/sdk/typescript, not from memory or ad-hoc probes alone. Current cutover validation targets exact `@cursor/sdk@1.0.27` and Pi 0.84.0 local packages.
|
|
261
261
|
|
|
262
262
|
## Pi provider SDK event capture
|
|
263
263
|
|
|
@@ -268,7 +268,7 @@ One-shot maintainer script (RPC pi run, gitignored artifacts by default):
|
|
|
268
268
|
```bash
|
|
269
269
|
CURSOR_API_KEY=... npm run debug:provider-events -- \
|
|
270
270
|
--cwd . \
|
|
271
|
-
--model cursor/
|
|
271
|
+
--model cursor/grok-4.6 \
|
|
272
272
|
--prompt 'Repro prompt here' \
|
|
273
273
|
--out .debug/cursor-sdk-events/manual-repro
|
|
274
274
|
```
|
|
@@ -308,7 +308,7 @@ Artifacts under `--out` (default `.debug/cursor-sdk-events/<timestamp>/` under `
|
|
|
308
308
|
During any normal pi session you can also opt in with:
|
|
309
309
|
|
|
310
310
|
```bash
|
|
311
|
-
PI_CURSOR_SDK_EVENT_DEBUG=1 pi --approve -e . --model cursor/
|
|
311
|
+
PI_CURSOR_SDK_EVENT_DEBUG=1 pi --approve -e . --model cursor/grok-4.6
|
|
312
312
|
```
|
|
313
313
|
|
|
314
314
|
Multi-turn sessions group automatically by pi session file:
|
|
@@ -359,7 +359,7 @@ Ask the reporter (or capture yourself) for:
|
|
|
359
359
|
| Field | Why |
|
|
360
360
|
| --- | --- |
|
|
361
361
|
| `pi --version` and installed `pi-cursor-sdk` version | Confirms extension/runtime in use |
|
|
362
|
-
| Model ID (for example `cursor/
|
|
362
|
+
| Model ID (for example `cursor/grok-4.6`) | Routing/replay behavior is model-scoped |
|
|
363
363
|
| Exact repro prompt and prior turns | Multi-turn replay history affects prompt text |
|
|
364
364
|
| Flags: `--cursor-no-fast`, `PI_CURSOR_PI_TOOL_BRIDGE`, `PI_CURSOR_EXPOSE_BUILTIN_TOOLS`, `PI_CURSOR_SETTING_SOURCES`, `PI_CURSOR_TOOL_MANIFEST` | Bridge vs native-only vs narrowed settings; bootstrap callable-surface manifest |
|
|
365
365
|
| Whether the listed names are `pi__*` bridge MCP, Cursor-native (`browser_navigate`, `WebSearch`), or `cursor-replay-*` replay IDs | Three different surfaces (see [Cursor native tool replay](./cursor-native-tool-replay.md#live-bridge-vs-replay)) |
|
|
@@ -380,7 +380,7 @@ chmod 600 "$SMOKE_DIR/home/.pi/agent/auth.json"
|
|
|
380
380
|
env -i HOME="$SMOKE_DIR/home" PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin" \
|
|
381
381
|
MISE_DISABLE=1 \
|
|
382
382
|
PI_CURSOR_PI_TOOL_BRIDGE_DEBUG=1 \
|
|
383
|
-
pi --approve -e . --cursor-no-fast --model cursor/
|
|
383
|
+
pi --approve -e . --cursor-no-fast --model cursor/grok-4.6 \
|
|
384
384
|
--session-dir "$SMOKE_DIR/session" \
|
|
385
385
|
-p '<exact reporter prompt>'
|
|
386
386
|
```
|
|
@@ -392,7 +392,7 @@ For pi parsing, replay routing, or bridge timing, prefer:
|
|
|
392
392
|
```bash
|
|
393
393
|
npm run debug:provider-events -- \
|
|
394
394
|
--cwd "$PWD" \
|
|
395
|
-
--model cursor/
|
|
395
|
+
--model cursor/grok-4.6 \
|
|
396
396
|
--prompt '<exact reporter prompt>' \
|
|
397
397
|
--out "$SMOKE_DIR/provider-events"
|
|
398
398
|
```
|
|
@@ -34,19 +34,19 @@ Default behavior:
|
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
36
|
# Disable only Cursor's interactive question tool
|
|
37
|
-
PI_CURSOR_ASK_QUESTION=0 pi --model cursor/
|
|
37
|
+
PI_CURSOR_ASK_QUESTION=0 pi --model cursor/grok-4.6
|
|
38
38
|
|
|
39
39
|
# Disable pi bridge entirely
|
|
40
|
-
PI_CURSOR_PI_TOOL_BRIDGE=0 pi --model cursor/
|
|
40
|
+
PI_CURSOR_PI_TOOL_BRIDGE=0 pi --model cursor/grok-4.6
|
|
41
41
|
|
|
42
42
|
# Expose overlapping pi builtins through the bridge
|
|
43
|
-
PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1 pi --model cursor/
|
|
43
|
+
PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1 pi --model cursor/grok-4.6
|
|
44
44
|
|
|
45
45
|
# Fail a stranded bridge call sooner than the effective MCP tool timeout
|
|
46
|
-
PI_CURSOR_PI_BRIDGE_CALL_TIMEOUT_MS=120000 pi --model cursor/
|
|
46
|
+
PI_CURSOR_PI_BRIDGE_CALL_TIMEOUT_MS=120000 pi --model cursor/grok-4.6
|
|
47
47
|
|
|
48
48
|
# Disable bootstrap tool manifest
|
|
49
|
-
PI_CURSOR_TOOL_MANIFEST=0 pi --model cursor/
|
|
49
|
+
PI_CURSOR_TOOL_MANIFEST=0 pi --model cursor/grok-4.6
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
## Runtime and transport policy
|
package/docs/platform-smoke.md
CHANGED
|
@@ -106,7 +106,7 @@ Rendering is host-side. Targets capture the real ANSI stream; the macOS host ren
|
|
|
106
106
|
|
|
107
107
|
## Target session model
|
|
108
108
|
|
|
109
|
-
Each target opens one Crabbox target session, syncs once, runs all suites for that target under one coherent target run id, collects artifacts, and stops/releases the target. The release-gate entrypoint runs required targets
|
|
109
|
+
Each target opens one Crabbox target session, syncs once, runs all suites for that target under one coherent target run id, collects artifacts, and stops/releases the target. The release-gate entrypoint runs required targets sequentially to prevent shared host, VM/container, and Cursor API contention; each target runs its own suites in order and fails fast within that target. Platform smoke disables Crabbox git-seed sync (`CRABBOX_SYNC_GIT_SEED=false`) so every run tests the current local checkout and uncommitted smoke-runner changes rather than a remote Git seed.
|
|
110
110
|
|
|
111
111
|
```text
|
|
112
112
|
start target session
|
|
@@ -135,13 +135,13 @@ start target session
|
|
|
135
135
|
end target session
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
-
The target session fails fast. The release-gate path warms one Crabbox lease
|
|
138
|
+
The target session fails fast. The release-gate path handles one target at a time: it warms one Crabbox lease, performs one fresh sync, runs suites in order, and stops that target after the first failure before starting the next target. Total wall time is therefore additive across required targets; this avoids nondeterministic worker loss and live-run stalls from competing VM/container load and concurrent Cursor API calls. Per-suite commands remain available for diagnosis, but they are intentionally not the normal release path because repeated warmup/sync/install cycles make releases too slow.
|
|
139
139
|
|
|
140
140
|
Runtime budget is part of the contract:
|
|
141
141
|
|
|
142
142
|
- `smoke:platform:doctor` never calls Cursor.
|
|
143
|
-
- `platform-build` runs once per target and is the only suite that performs the full local CI/build/typecheck/package gate. Its
|
|
144
|
-
- Live suites reuse the target checkout and prepared `node_modules` when run after `platform-build`; they do not repeat `npm ci` in a target-session release run.
|
|
143
|
+
- `platform-build` runs once per target and is the only suite that performs the full local CI/build/typecheck/package gate. Its target invocations raise only Vitest's default per-test timeout to 15 seconds for host-contention headroom; normal `npm test` keeps the 5-second default, and explicit longer integration-test timeouts still apply.
|
|
144
|
+
- Live suites reuse the target checkout and prepared `node_modules` when run after `platform-build`; they do not repeat `npm ci` in a target-session release run. Their interactive Pi process sets `PI_OFFLINE=1` to skip unrelated startup catalog/update probes; the scenario's explicit Cursor provider turn remains live. The PTY launches Pi's JavaScript entry with direct Node argv so multiline prompts remain one positional message on every target.
|
|
145
145
|
- Live and local-resume suites share one target-local packed-install prep directory per target-session release run. The first such suite runs `npm pack` and `npm install --no-save <tarball>` once. Visual/abort suites install that packed path with `pi install --approve -l`; local-resume lanes pass the same packed package path to their source-tree smoke harness instead of loading the checkout extension.
|
|
146
146
|
- Visual coverage is batched into one native prompt, one focused HTTP/1.1 transport prompt, one bridge prompt, and one abort/cleanup prompt per target. Do not split the card matrices into one prompt per card.
|
|
147
147
|
- The gate is fail-fast by target to avoid burning Cursor calls after a platform has already failed.
|
|
@@ -164,7 +164,7 @@ Cloud validation stays separate from `smoke:platform:all`. Releases that touch a
|
|
|
164
164
|
npm run smoke:cloud
|
|
165
165
|
```
|
|
166
166
|
|
|
167
|
-
The no-flag command is the required `cursor/
|
|
167
|
+
The no-flag command is the required `cursor/grok-4.6` matrix. It uses current `gh` CLI authentication to create one private throwaway GitHub repository, seeds clean `main`, `starting-ref`, and `direct-push` branches, and runs persisted-session named lanes for:
|
|
168
168
|
|
|
169
169
|
- cancellation, with exact agent/run IDs captured before abort, retained `runIdSource` (`metadata` or installed-SDK `Agent.listRuns()` recovery), and terminal `cancelled` independently read through the SDK;
|
|
170
170
|
- explicit HTTPS repository plus `startingRef`, requiring a distinct pushed cloud branch with remote-content and starting-ref-ancestry proof, recording whether the SDK returned branch metadata, and validating any returned PR URL through GitHub;
|
|
@@ -288,7 +288,7 @@ import { LOCAL_RESUME_SUITE_NAMES } from "./scripts/platform-smoke/local-resume-
|
|
|
288
288
|
|
|
289
289
|
export default {
|
|
290
290
|
packageName: "pi-cursor-sdk",
|
|
291
|
-
cursorModel: "cursor/
|
|
291
|
+
cursorModel: "cursor/grok-4.6",
|
|
292
292
|
artifactRoot: ".artifacts/platform-smoke",
|
|
293
293
|
artifactRetention: {
|
|
294
294
|
maxRunDirs: 18,
|
|
@@ -26,7 +26,12 @@ function normalize (uri, options) {
|
|
|
26
26
|
*/
|
|
27
27
|
function resolve (baseURI, relativeURI, options) {
|
|
28
28
|
const schemelessOptions = options ? Object.assign({ scheme: 'null' }, options) : { scheme: 'null' }
|
|
29
|
-
const
|
|
29
|
+
const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions)
|
|
30
|
+
const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions)
|
|
31
|
+
if (baseMalformed || relativeMalformed) {
|
|
32
|
+
throw new Error(baseParsed.error || relativeParsed.error || 'URI is malformed.')
|
|
33
|
+
}
|
|
34
|
+
const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true)
|
|
30
35
|
schemelessOptions.skipEscape = true
|
|
31
36
|
return serialize(resolved, schemelessOptions)
|
|
32
37
|
}
|
|
@@ -206,6 +211,15 @@ const URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:
|
|
|
206
211
|
// with or without a scheme prefix, for the literal-backslash rejection below.
|
|
207
212
|
const AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/
|
|
208
213
|
|
|
214
|
+
// Captures the leading authority-introducer region after an optional scheme: a
|
|
215
|
+
// run of forward slashes, backslashes, and the characters the WHATWG URL parser
|
|
216
|
+
// removes before parsing (TAB U+0009, LF U+000A, CR U+000D). A valid introducer
|
|
217
|
+
// is exactly "//". Node treats "\" as "/" on special schemes and strips those
|
|
218
|
+
// characters first, so forms like "\\", "/\", "\/", "/<TAB>/", or a leading
|
|
219
|
+
// "<TAB>//" reach an authority in Node while fast-uri's URI_PARSE folds them into
|
|
220
|
+
// the path group (host confusion / SSRF / redirect bypass).
|
|
221
|
+
const AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/
|
|
222
|
+
|
|
209
223
|
/**
|
|
210
224
|
* @param {import('./types/index').URIComponent} parsed
|
|
211
225
|
* @param {RegExpMatchArray} matches
|
|
@@ -265,6 +279,28 @@ function parseWithStatus (uri, opts) {
|
|
|
265
279
|
malformedAuthorityOrPort = true
|
|
266
280
|
}
|
|
267
281
|
|
|
282
|
+
// Reject a malformed or whitespace-smuggled authority introducer. fast-uri
|
|
283
|
+
// only recognizes a literal "//"; anything else in the leading separator run
|
|
284
|
+
// (a backslash, or a "//" that appears only after removing the TAB/LF/CR that
|
|
285
|
+
// Node strips) means the authority fast-uri parses differs from the one Node's
|
|
286
|
+
// URL resolves. Reject rather than rewrite, mirroring the literal-backslash
|
|
287
|
+
// guard above. Percent-encoded forms (%5C, %09) are untouched, valid data.
|
|
288
|
+
const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION)
|
|
289
|
+
if (introducerMatch !== null) {
|
|
290
|
+
const region = introducerMatch[1]
|
|
291
|
+
const normalizedRegion = region.replace(/[\t\n\r]/g, '')
|
|
292
|
+
// Two or more leading separators introduce an authority.
|
|
293
|
+
if (normalizedRegion.length >= 2) {
|
|
294
|
+
if (normalizedRegion.slice(0, 2) !== '//') {
|
|
295
|
+
parsed.error = parsed.error || 'URI authority must not contain a literal backslash.'
|
|
296
|
+
malformedAuthorityOrPort = true
|
|
297
|
+
} else if (region.length !== normalizedRegion.length) {
|
|
298
|
+
parsed.error = parsed.error || 'URI authority introducer must not contain whitespace.'
|
|
299
|
+
malformedAuthorityOrPort = true
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
268
304
|
const matches = uri.match(URI_PARSE)
|
|
269
305
|
|
|
270
306
|
if (matches) {
|
|
@@ -221,3 +221,139 @@ test('parse does not reject a literal backslash in the query or fragment', (t) =
|
|
|
221
221
|
t.notOk(parsed.error, 'backslash in query/fragment does not mark the URI malformed')
|
|
222
222
|
t.equal(parsed.host, 'host.example.com', 'host parsed normally')
|
|
223
223
|
})
|
|
224
|
+
|
|
225
|
+
test('parse rejects a malformed authority introducer (\\\\, /\\, \\/) in place of //', (t) => {
|
|
226
|
+
// Regression: "\\", "/\\", "\\/" after the scheme colon are not valid authority
|
|
227
|
+
// introducers. Node's URL treats "\\" as interchangeable with "/" on special
|
|
228
|
+
// schemes, so "http:\\\\evil.com/path" would be parsed as host "evil.com" by
|
|
229
|
+
// Node, but fast-uri must reject it as malformed to prevent SSRF/redirect bypass.
|
|
230
|
+
const cases = [
|
|
231
|
+
'http:\\\\evil.com/path',
|
|
232
|
+
'http:/\\evil.com/path',
|
|
233
|
+
'http:\\/evil.com/path',
|
|
234
|
+
'ws:\\\\evil.com/chat',
|
|
235
|
+
'wss:\\\\evil.com/chat',
|
|
236
|
+
'ftp:\\\\evil.com/',
|
|
237
|
+
'\\\\evil.com/path'
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
t.plan(cases.length)
|
|
241
|
+
|
|
242
|
+
cases.forEach((input) => {
|
|
243
|
+
t.equal(
|
|
244
|
+
fastURI.parse(input).error,
|
|
245
|
+
'URI authority must not contain a literal backslash.',
|
|
246
|
+
input
|
|
247
|
+
)
|
|
248
|
+
})
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
test('normalize does not canonicalize a malformed-authority-introducer URI', (t) => {
|
|
252
|
+
const cases = [
|
|
253
|
+
'http:\\\\evil.com/path',
|
|
254
|
+
'http:/\\evil.com/path'
|
|
255
|
+
]
|
|
256
|
+
|
|
257
|
+
t.plan(cases.length)
|
|
258
|
+
|
|
259
|
+
cases.forEach((input) => {
|
|
260
|
+
t.equal(fastURI.normalize(input), input, input)
|
|
261
|
+
})
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
test('equal returns false for malformed-authority-introducer URIs', (t) => {
|
|
265
|
+
const pairs = [
|
|
266
|
+
['http:\\\\evil.com/path', 'http://evil.com/path'],
|
|
267
|
+
['http:/\\evil.com/path', 'http://evil.com/path']
|
|
268
|
+
]
|
|
269
|
+
|
|
270
|
+
t.plan(pairs.length)
|
|
271
|
+
|
|
272
|
+
pairs.forEach(([left, right]) => {
|
|
273
|
+
t.equal(fastURI.equal(left, right), false, `${left} != ${right}`)
|
|
274
|
+
})
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
test('resolve throws on malformed authority introducer', (t) => {
|
|
278
|
+
// resolve() returns a plain string with no error field, so the only safe
|
|
279
|
+
// behavior is to throw when either component has a malformed authority.
|
|
280
|
+
const pairs = [
|
|
281
|
+
['https://allowed.com/', '\\\\evil.com/path'],
|
|
282
|
+
['\\\\evil.com/path', 'https://allowed.com/'],
|
|
283
|
+
['https://allowed.com/', 'http:/\\evil.com/path'],
|
|
284
|
+
['https://allowed.com/', 'http:\\/evil.com/path']
|
|
285
|
+
]
|
|
286
|
+
|
|
287
|
+
t.plan(pairs.length)
|
|
288
|
+
|
|
289
|
+
pairs.forEach(([base, rel]) => {
|
|
290
|
+
t.throws(
|
|
291
|
+
() => fastURI.resolve(base, rel),
|
|
292
|
+
/URI authority must not contain a literal backslash/,
|
|
293
|
+
`${base} + ${rel}`
|
|
294
|
+
)
|
|
295
|
+
})
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
test('parse rejects a whitespace-split authority introducer (TAB, LF, CR)', (t) => {
|
|
299
|
+
// The WHATWG URL parser removes TAB (U+0009), LF (U+000A) and CR (U+000D) from
|
|
300
|
+
// the input before parsing, so a stripped character wedged into the introducer
|
|
301
|
+
// ("/<TAB>\\", "/<TAB>/", or a leading "<TAB>//") reaches an authority in Node
|
|
302
|
+
// while fast-uri would otherwise fold it into the path. These must be rejected
|
|
303
|
+
// like the adjacent "\\", "/\\", "\\/" forms.
|
|
304
|
+
const cases = [
|
|
305
|
+
{ input: '/\t\\evil.com/path', expectedError: 'URI authority must not contain a literal backslash.' },
|
|
306
|
+
{ input: '/\t/evil.com/path', expectedError: 'URI authority introducer must not contain whitespace.' },
|
|
307
|
+
{ input: '/\n\\evil.com/path', expectedError: 'URI authority must not contain a literal backslash.' },
|
|
308
|
+
{ input: '/\r\\evil.com/path', expectedError: 'URI authority must not contain a literal backslash.' },
|
|
309
|
+
{ input: '\t//evil.com/path', expectedError: 'URI authority introducer must not contain whitespace.' },
|
|
310
|
+
{ input: '\t/\\evil.com/path', expectedError: 'URI authority must not contain a literal backslash.' },
|
|
311
|
+
{ input: 'https:/\t/evil.com/path', expectedError: 'URI authority introducer must not contain whitespace.' }
|
|
312
|
+
]
|
|
313
|
+
|
|
314
|
+
t.plan(cases.length)
|
|
315
|
+
|
|
316
|
+
cases.forEach(({ input, expectedError }) => {
|
|
317
|
+
t.equal(fastURI.parse(input).error, expectedError, JSON.stringify(input))
|
|
318
|
+
})
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
test('resolve throws on a whitespace-split authority introducer', (t) => {
|
|
322
|
+
const pairs = [
|
|
323
|
+
['https://allowed.com/', '/\t\\evil.com/path'],
|
|
324
|
+
['https://allowed.com/', '/\t/evil.com/path'],
|
|
325
|
+
['https://allowed.com/', '/\n\\evil.com/path'],
|
|
326
|
+
['/\t/evil.com/path', 'https://allowed.com/']
|
|
327
|
+
]
|
|
328
|
+
|
|
329
|
+
t.plan(pairs.length)
|
|
330
|
+
|
|
331
|
+
pairs.forEach(([base, rel]) => {
|
|
332
|
+
t.throws(
|
|
333
|
+
() => fastURI.resolve(base, rel),
|
|
334
|
+
/URI authority (must not contain a literal backslash|introducer must not contain whitespace)/,
|
|
335
|
+
`${JSON.stringify(base)} + ${JSON.stringify(rel)}`
|
|
336
|
+
)
|
|
337
|
+
})
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
test('parse does not reject valid authority introducer patterns', (t) => {
|
|
341
|
+
// No false positives: "//" introducer and scheme-less "//" must be valid.
|
|
342
|
+
const cases = [
|
|
343
|
+
'http://good.com/',
|
|
344
|
+
'https://good.com/',
|
|
345
|
+
'ws://good.com/chat',
|
|
346
|
+
'wss://good.com/chat',
|
|
347
|
+
'ftp://good.com/',
|
|
348
|
+
'//good.com/path',
|
|
349
|
+
'/absolute/path',
|
|
350
|
+
'relative/path'
|
|
351
|
+
]
|
|
352
|
+
|
|
353
|
+
t.plan(cases.length)
|
|
354
|
+
|
|
355
|
+
cases.forEach((input) => {
|
|
356
|
+
const parsed = fastURI.parse(input)
|
|
357
|
+
t.notOk(parsed.error, input)
|
|
358
|
+
})
|
|
359
|
+
})
|