@bastani/atomic 0.8.28-alpha.4 → 0.8.29-alpha.2

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.
Files changed (134) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/dist/builtin/cursor/CHANGELOG.md +27 -0
  3. package/dist/builtin/cursor/LICENSE +26 -0
  4. package/dist/builtin/cursor/README.md +22 -0
  5. package/dist/builtin/cursor/index.ts +9 -0
  6. package/dist/builtin/cursor/package.json +46 -0
  7. package/dist/builtin/cursor/src/auth.ts +352 -0
  8. package/dist/builtin/cursor/src/catalog-cache.ts +155 -0
  9. package/dist/builtin/cursor/src/config.ts +123 -0
  10. package/dist/builtin/cursor/src/conversation-state.ts +135 -0
  11. package/dist/builtin/cursor/src/cursor-models-raw.json +583 -0
  12. package/dist/builtin/cursor/src/model-mapper.ts +270 -0
  13. package/dist/builtin/cursor/src/models.ts +54 -0
  14. package/dist/builtin/cursor/src/native-loader.ts +71 -0
  15. package/dist/builtin/cursor/src/proto/README.md +34 -0
  16. package/dist/builtin/cursor/src/proto/agent_pb.ts +15294 -0
  17. package/dist/builtin/cursor/src/proto/protobuf-codec.ts +717 -0
  18. package/dist/builtin/cursor/src/provider.ts +301 -0
  19. package/dist/builtin/cursor/src/stream.ts +564 -0
  20. package/dist/builtin/cursor/src/transport.ts +791 -0
  21. package/dist/builtin/intercom/CHANGELOG.md +10 -0
  22. package/dist/builtin/intercom/package.json +2 -2
  23. package/dist/builtin/intercom/skills/intercom/SKILL.md +5 -5
  24. package/dist/builtin/mcp/CHANGELOG.md +10 -0
  25. package/dist/builtin/mcp/package.json +3 -3
  26. package/dist/builtin/subagents/CHANGELOG.md +18 -0
  27. package/dist/builtin/subagents/README.md +7 -3
  28. package/dist/builtin/subagents/agents/codebase-online-researcher.md +9 -24
  29. package/dist/builtin/subagents/agents/debugger.md +3 -5
  30. package/dist/builtin/subagents/package.json +4 -4
  31. package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +2 -1
  32. package/dist/builtin/subagents/src/runs/foreground/execution.ts +2 -1
  33. package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +1 -0
  34. package/dist/builtin/subagents/src/runs/shared/pi-args.ts +19 -2
  35. package/dist/builtin/subagents/src/runs/shared/structured-output.ts +271 -10
  36. package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +12 -39
  37. package/dist/builtin/subagents/src/shared/types.ts +1 -0
  38. package/dist/builtin/subagents/src/shared/utils.ts +50 -10
  39. package/dist/builtin/subagents/src/slash/saved-chain-mapping.ts +77 -0
  40. package/dist/builtin/subagents/src/slash/slash-commands.ts +1 -55
  41. package/dist/builtin/web-access/CHANGELOG.md +11 -1
  42. package/dist/builtin/web-access/README.md +1 -1
  43. package/dist/builtin/web-access/github-extract.ts +1 -1
  44. package/dist/builtin/web-access/package.json +3 -3
  45. package/dist/builtin/workflows/CHANGELOG.md +44 -0
  46. package/dist/builtin/workflows/README.md +19 -1
  47. package/dist/builtin/workflows/package.json +2 -2
  48. package/dist/builtin/workflows/skills/research-codebase/SKILL.md +17 -3
  49. package/dist/builtin/workflows/src/extension/wiring.ts +17 -1
  50. package/dist/builtin/workflows/src/extension/workflow-schema.ts +34 -0
  51. package/dist/builtin/workflows/src/runs/foreground/executor.ts +13 -2
  52. package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +86 -14
  53. package/dist/builtin/workflows/src/shared/authoring-contract.d.ts +11 -3
  54. package/dist/builtin/workflows/src/shared/types.ts +8 -4
  55. package/dist/builtin/workflows/src/tui/overlay-adapter.ts +64 -2
  56. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +8 -8
  57. package/dist/builtin/workflows/src/tui/workflow-status.ts +2 -0
  58. package/dist/core/builtin-packages.d.ts.map +1 -1
  59. package/dist/core/builtin-packages.js +6 -0
  60. package/dist/core/builtin-packages.js.map +1 -1
  61. package/dist/core/extensions/index.d.ts +1 -1
  62. package/dist/core/extensions/index.d.ts.map +1 -1
  63. package/dist/core/extensions/index.js.map +1 -1
  64. package/dist/core/extensions/types.d.ts +20 -0
  65. package/dist/core/extensions/types.d.ts.map +1 -1
  66. package/dist/core/extensions/types.js.map +1 -1
  67. package/dist/core/model-resolver.d.ts +1 -0
  68. package/dist/core/model-resolver.d.ts.map +1 -1
  69. package/dist/core/model-resolver.js +17 -8
  70. package/dist/core/model-resolver.js.map +1 -1
  71. package/dist/core/package-manager.d.ts +11 -9
  72. package/dist/core/package-manager.d.ts.map +1 -1
  73. package/dist/core/package-manager.js +55 -10
  74. package/dist/core/package-manager.js.map +1 -1
  75. package/dist/core/project-trust.d.ts +1 -0
  76. package/dist/core/project-trust.d.ts.map +1 -1
  77. package/dist/core/project-trust.js +3 -3
  78. package/dist/core/project-trust.js.map +1 -1
  79. package/dist/core/resource-loader.d.ts +9 -0
  80. package/dist/core/resource-loader.d.ts.map +1 -1
  81. package/dist/core/resource-loader.js +72 -9
  82. package/dist/core/resource-loader.js.map +1 -1
  83. package/dist/core/sdk.d.ts +3 -3
  84. package/dist/core/sdk.d.ts.map +1 -1
  85. package/dist/core/sdk.js +5 -5
  86. package/dist/core/sdk.js.map +1 -1
  87. package/dist/core/tools/index.d.ts +1 -0
  88. package/dist/core/tools/index.d.ts.map +1 -1
  89. package/dist/core/tools/index.js +1 -0
  90. package/dist/core/tools/index.js.map +1 -1
  91. package/dist/core/tools/structured-output.d.ts +39 -0
  92. package/dist/core/tools/structured-output.d.ts.map +1 -0
  93. package/dist/core/tools/structured-output.js +141 -0
  94. package/dist/core/tools/structured-output.js.map +1 -0
  95. package/dist/index.d.ts +1 -1
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +1 -1
  98. package/dist/index.js.map +1 -1
  99. package/dist/main.d.ts.map +1 -1
  100. package/dist/main.js +36 -14
  101. package/dist/main.js.map +1 -1
  102. package/dist/modes/interactive/components/login-dialog.d.ts +3 -0
  103. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  104. package/dist/modes/interactive/components/login-dialog.js +16 -0
  105. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  106. package/dist/modes/interactive/interactive-mode.d.ts +11 -0
  107. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  108. package/dist/modes/interactive/interactive-mode.js +158 -11
  109. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  110. package/dist/modes/print-mode.d.ts.map +1 -1
  111. package/dist/modes/print-mode.js +39 -0
  112. package/dist/modes/print-mode.js.map +1 -1
  113. package/docs/custom-provider.md +1 -0
  114. package/docs/extensions.md +2 -2
  115. package/docs/models.md +2 -0
  116. package/docs/packages.md +3 -1
  117. package/docs/providers.md +15 -0
  118. package/docs/sdk.md +61 -0
  119. package/docs/security.md +1 -1
  120. package/docs/subagents.md +21 -0
  121. package/docs/usage.md +2 -0
  122. package/docs/workflows.md +10 -7
  123. package/examples/extensions/README.md +1 -1
  124. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  125. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  126. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  127. package/examples/extensions/gondolin/package-lock.json +2 -2
  128. package/examples/extensions/gondolin/package.json +1 -1
  129. package/examples/extensions/sandbox/package-lock.json +2 -2
  130. package/examples/extensions/sandbox/package.json +1 -1
  131. package/examples/extensions/structured-output.ts +22 -53
  132. package/examples/extensions/with-deps/package-lock.json +2 -2
  133. package/examples/extensions/with-deps/package.json +1 -1
  134. package/package.json +12 -9
package/CHANGELOG.md CHANGED
@@ -2,6 +2,81 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ### Added
6
+
7
+ - Added support for local `-e <dir>` extension sources to borrow project-local Atomic resources from `<dir>/.atomic`, legacy `<dir>/.pi`, and `<dir>/.agents/skills` after resolving trust for that extension source, preserving package-manager provenance and explicit-path workflow forwarding while avoiding untrusted borrowed resources ([#1354](https://github.com/bastani-inc/atomic/issues/1354)).
8
+ - Added a prototype Rust/N-API Cursor HTTP/2 native transport binding through the generated `@bastani/atomic-natives` NAPI-RS package, so Atomic can use an in-process native HTTP/2 client without requiring Node.js on `PATH`.
9
+ - Added the experimental bundled `@bastani/cursor` provider scaffold so `/login` can offer Cursor OAuth, estimated fallback exposes `cursor/composer-2`, and Cursor model mapping/streaming hooks are available behind an isolated HTTP/2 Connect transport boundary with production-default protobuf decoding, buffered Connect frames, write-before-headers Run streaming, stable Cursor conversation ids, schema-correct Cursor MCP tool advertisement, Cursor MCP tool-call decoding with protobuf `Value` or raw UTF-8/JSON arguments and exec-id metadata, same-stream MCP tool-result resume, abort/idle cleanup for paused tool streams, Connect end-stream error classification, exact live model id fidelity without static default injection, fast/thinking catalog grouping, and usage-delta accumulation.
10
+ - Added the opt-in `createStructuredOutputTool({ schema, capture, output, name })` factory for terminating machine-readable final answers with schema-as-parameters validation, flat `details`, in-process capture, flat private `output.json` capture plus `output.meta.json` sidecar metadata, and prompt guidance for exact-once use without registering `structured_output` in normal agent sessions by default ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
11
+
12
+ ### Changed
13
+
14
+ - Bumped the bundled upstream pi runtime libraries `@earendil-works/pi-agent-core`, `@earendil-works/pi-ai`, and `@earendil-works/pi-tui` from `^0.79.1` to `^0.79.3`, bringing the latest upstream provider, model, agent-core, and TUI compatibility fixes into `@bastani/atomic`.
15
+ - Updated the structured-output extension example and SDK/workflow/extension docs to use the canonical factory instead of hand-rolled `terminate: true` wrappers, and documented that schema-specific calls pass fields directly rather than through `{ value: ... }` ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
16
+ - Enforced top-level object schemas for `structured_output` factory registration, with guidance to wrap array or primitive final values in object fields, made custom-named factory tools advertise the configured name in prompt metadata, and documented that text print mode recognizes factory-created custom structured-output tools without treating every terminating tool as printable ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
17
+ - Clarified SDK, extension, and workflow guidance that structured-output tools are opt-in custom tools, with workflow stages/tasks/chains/parallel items receiving `structured_output` only when they declare a `schema` and subagent children receiving it only when `outputSchema` is enabled ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
18
+
19
+ ### Fixed
20
+
21
+ - Fixed the root `@bastani/atomic` package export to include a `default` condition alongside the ESM import target, improving compatibility with loaders that select default export conditions.
22
+ - Fixed extension custom UI focus deferral so full-screen overlays can keep keyboard focus while a parent/main-chat inline custom UI is pending, then focus that pending UI when the overlay is hidden; already-aborted custom UI calls no longer invoke factories or emit host custom-UI state changes ([#1353](https://github.com/bastani-inc/atomic/issues/1353)).
23
+ - Fixed text print mode to emit trailing terminating JSON from factory-created structured-output tools, including custom tool names such as `final_decision`, instead of only recognizing the canonical `structured_output` name ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
24
+ - Fixed terminating `structured_output` results to opt out of oversized-result persistence so large final JSON stays inline instead of being replaced by a `<persisted-output>` pointer ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
25
+ - Fixed cross-process structured-output file capture to preserve flat schema-valid params in `output.json` and write call metadata to an `output.meta.json` sidecar so parent readback can reject stale or non-final captures instead of accepting any schema-valid `output.json` payload by existence alone ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
26
+ - Fixed bundled subagent handling so explicit empty `tools: []` plus `outputSchema` grants only the schema-backed `structured_output` runtime tool instead of restoring default tools ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
27
+ - Fixed prerelease publishing for native Atomic artifacts by allowing the `@bastani/atomic-natives` package metadata in release-preparation verification, running native artifact builds on architecture-matched Blacksmith and macOS runners, and documenting the two-package publish flow while keeping npm provenance publishing on GitHub-hosted Ubuntu.
28
+ - Fixed the bundled experimental Cursor provider to honor per-request stream deadlines across open/read/resume writes, reset timed-out or aborted streams, clean up replaced paused turns safely, catch cleanup cancellation failures, tolerate non-MCP Cursor exec protocol messages without ending assistant turns, and align Run requests with Cursor's private CLI protocol by using blob/KV conversation state plus request-context tool-definition responses without the unsupported custom system-prompt field ([#1286](https://github.com/bastani-inc/atomic/issues/1286)).
29
+ - Fixed release archive startup for the bundled experimental Cursor provider by declaring `@bufbuild/protobuf` as an `@bastani/atomic` runtime dependency, covering Cursor in the bundled-package dependency metadata guard, and smoke-checking Cursor/protobuf assets in native archives ([#1286](https://github.com/bastani-inc/atomic/issues/1286)).
30
+
31
+ ### Security
32
+
33
+ - Kept Cursor credentials OAuth-only with token/header and PKCE poll-secret redaction and no localhost proxy, while moving Cursor HTTP/2 to a bundled Rust/N-API native binding and no longer sending the current working directory as `previousWorkspaceUris` by default.
34
+
35
+ ## [0.8.28] - 2026-06-11
36
+
37
+ ### Added
38
+
39
+ - Added optional inline free-form text entry to the `ask_user_question` TUI's **Chat about this** footer row. Non-empty typed chat text now returns as a `kind: "chat"` answer surfaced to the agent without the legacy stop/wait termination envelope, while empty submissions keep the existing sentinel behavior.
40
+ - Added session-scoped `bashPolicy` support for the built-in `bash` tool, with exact/prefix/command-string-glob/regex rules, deny-over-allow precedence, segment-aware parsing by default, fail-closed validation of invalid policies, and conservative rejection of compound heads, redirections, assignments, and non-literal command heads before shell execution.
41
+ - Ported the upstream project-trust store and resolver foundation: project trust decisions are remembered, `--approve`/`--no-approve` affect runtime trust state, untrusted sessions skip project-local extensions/resources/context/system-prompt discovery and refuse project-setting writes, startup migrations and project config reads are trust-gated, and a new `/trust` slash command with the upstream `TrustSelectorComponent` lets saved project-trust decisions be reviewed and changed in-session.
42
+ - Added upstream pi 0.76.0-0.79.1 coding-agent compatibility exports for package asset path helpers, CLI argument parsing (`Args`, `parseArgs`), `SettingsManagerCreateOptions`, image conversion (`convertToPng`), and RPC extension UI request/response types, plus the shared JSON comment/trailing-comma stripping utility used by model configuration migrations.
43
+ - Added the upstream `project-trust`, `git-merge-and-resolve`, `input-transform-streaming`, and Gondolin tool-routing example extensions adapted to Atomic package identity, shared `warnDeprecation`/`openBrowser` utilities, upstream `docs/security.md` and `docs/containerization.md` rebranded for Atomic, and extensive upstream regression coverage.
44
+
45
+ ### Changed
46
+
47
+ - Changed Atomic compaction to be verbatim-only across manual `/compact`, automatic threshold/overflow compaction, SDK/RPC compaction, and extension-triggered compaction. All compaction now records validated `context_compaction` deletion targets and rebuilds active context with retained transcript content verbatim and unchanged; retained file paths, exact commands, error strings, and line numbers are never paraphrased.
48
+ - Changed compaction extension hooks (`session_before_compact`, `session_compact`) to receive verbatim context-compaction preparations/results and allow cancellation or locally validated deletion requests instead of custom generated summaries.
49
+ - Changed the verbatim compaction critical-overflow recovery prompt to evict in an explicit priority order (removable reasoning traces first, then removable user/custom/summary context) while preserving existing safety/retention rules ([#1308](https://github.com/bastani-inc/atomic/issues/1308)).
50
+ - Changed the bundled builtin `deep-research-codebase`, `goal`, `ralph`, and `open-claude-design` workflows to use `anthropic/claude-fable-5:xhigh` as the primary planner/reviewer/design model, demoting each previous primary to the head of the fallback chain ([#1345](https://github.com/bastani-inc/atomic/pull/1345)).
51
+ - Bumped the bundled upstream pi libraries `@earendil-works/pi-agent-core`, `@earendil-works/pi-ai`, and `@earendil-works/pi-tui` from `^0.78.1` to `^0.79.1`, bringing in Claude Fable 5 and Azure metadata updates, GPT-5 token/context metadata fixes, provider thinking-payload compatibility updates, autocomplete/CJK prompt rendering fixes, and keyboard-protocol fallback improvements.
52
+ - Ported upstream prompt-template argument default handling and added `${N:-default}` positional default support in prompt templates, matching upstream slash-template substitution behavior without recursively expanding argument/default values.
53
+
54
+ ### Fixed
55
+
56
+ - Fixed oversized tool-call results flooding model context by persisting large results to disk (`<sessionDir>/tool-results/<toolCallId>.txt`) and returning a compact `<persisted-output>` message with the file path and a 2KB head preview when a result exceeds the 50,000-character system cap or a lower per-tool cap; tools can opt out via `maxResultSizeChars: Infinity`, and persistence degrades gracefully for images or write failures ([#1322](https://github.com/bastani-inc/atomic/issues/1322)).
57
+ - Fixed the Read tool to block text file-read results above 50,000 characters and return incremental-read guidance, including byte-slice guidance for oversized single-line selections ([#1323](https://github.com/bastani-inc/atomic/issues/1323)).
58
+ - Fixed `AgentSession.prompt` surfacing the confusing `No API key found for undefined` error when a model never resolved to a real provider; the prompt path now fails fast with a clear `Unknown model: "<id>" did not resolve to an available provider` message.
59
+ - Hardened prompt-template argument substitution against polynomial-time regex backtracking (ReDoS) by length-bounding the `${N:-default}` default-value capture.
60
+ - Fixed provider auth-status reporting for explicit `$ENV_VAR` config values, preserved uppercase literal credentials during config-value migrations (including legacy `~/.pi/agent` roots), preserved `models.json` JSONC comments/formatting during migration, and accepted the upstream `supportsDeveloperRole` flag for custom OpenAI Responses models.
61
+ - Fixed RPC client requests to reject promptly when the child agent process exits or its stdio fails, completed the RPC-mode output/backpressure and `excludeFromContext` bash-command port, and preserved steering/follow-up queue modes across extension-triggered RPC session reloads.
62
+ - Fixed SDK provider stream options so HTTP idle timeouts and WebSocket connect timeouts from settings are forwarded to provider streams while preserving per-request overrides.
63
+ - Fixed interactive startup input handling so prompts submitted before the main input loop is installed are queued instead of dropped, and fixed signal-triggered shutdown ordering so extension `session_shutdown` cleanup runs before terminal restore writes.
64
+ - Fixed the initial `--resume` session picker and all-sessions pane to honor a custom `--session-dir`.
65
+ - Fixed plain metadata commands (`--version`, `--help`, `--list-models`) to keep their output on stdout for scripts/completions while keeping auto-install/startup chatter off stdout.
66
+ - Fixed OAuth login dialog prompt/manual input rendering so submitted values remain stable, auth storage writes to consistently use `0600` file mode, and self-update command generation to bypass package-manager minimum-release-age delays.
67
+ - Fixed changelog link normalization to produce Atomic repository/tag-pinned links from local package links and legacy pi-mono URLs, wired into startup and `/changelog` output.
68
+ - Fixed WSL repositories on Windows-mounted paths to poll Git `HEAD` changes so the footer branch display updates reliably, plus footer cache-hit-rate display, settings selector default project-trust editing, tool self-render image rendering, and collapsed tool-output hint styling.
69
+ - Rebranded provider attribution headers (OpenRouter, NVIDIA NIM) and the Gondolin VM session label to Atomic identity, matched OpenRouter-compatible custom endpoints by exact hostname, and corrected README/RPC/session-format/SDK/example docs to use `atomic`, `ATOMIC_*`, and `.atomic` as primary with legacy `PI_*`/`.pi` labeled as such.
70
+ - Fixed extension command contexts to expose live base system-prompt options, hid `streamingBehavior` from idle input handlers, continued agent turns for follow-ups queued during `agent_end` handlers, and ported upstream tool path rendering with terminal hyperlink support for edit tool output.
71
+
72
+ ### Removed
73
+
74
+ - Removed the legacy summary-compaction runtime path, summary prompts, `CompactionEntry` active-context injection, `CompactionSummaryMessage` active message type, custom compaction instructions (`CompactOptions.customInstructions`, RPC `compact.customInstructions`, `/compact [instructions]`), `compaction.keepRecentTokens` setting, summary-compaction public exports, and summary-compaction docs and examples. Historical `type:"compaction"` JSONL lines on disk are inert and are not injected into active LLM context.
75
+
76
+ ### Security
77
+
78
+ - Bumped the transitive `shell-quote` dependency from `1.8.3` to `1.8.4` in the `examples/extensions/sandbox` lockfile, resolving the critical advisory [GHSA-w7jw-789q-3m8p](https://github.com/advisories/GHSA-w7jw-789q-3m8p).
79
+
5
80
  ## [0.8.28-alpha.4] - 2026-06-11
6
81
 
7
82
  ### Changed
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ### Added
6
+
7
+ - Added a prototype Rust/N-API HTTP/2 native binding and loader so the Cursor transport uses the generated `@bastani/atomic-natives` NAPI-RS package without requiring Node.js on `PATH`.
8
+ - Added the `@bastani/cursor` bundled provider scaffold with Cursor PKCE OAuth, token refresh, estimated/live model mapping, transport isolation, stream adapter hooks, lifecycle cleanup, and fake-transport tests.
9
+ - Added a safe production UUID generator path for Cursor login, refresh, and streaming, plus an injectable HTTP/2 Connect transport boundary with frame helpers and protocol-codec seams for live Cursor RPC work.
10
+ - Added the production-default minimal Cursor protobuf codec, buffered Connect frame decoder, HTTP/2 non-2xx/session/stream lifecycle error classification, and stricter live-discovery fallback policy.
11
+ - Hardened Cursor Run streaming to write the initial Connect frame before response headers, eagerly observe stream/session terminal events, encode conversation/tool context, decode Cursor `execServerMessage.mcpArgs` tool calls with protobuf `Value` arguments and field-order-independent exec ids, classify Connect end-stream errors, parse checkpoint token details without counting `max_tokens` as output, and accumulate usage deltas without zeroing missing checkpoint fields.
12
+ - Preserved live Cursor model id fidelity by exposing advertised fast/thinking ids instead of synthesizing absent base ids.
13
+ - Added a token-free live model catalog cache with atomic writes, cached startup registration, best-effort auth-first refresh discovery, and bounded first-stream rediscovery cleanup.
14
+ - Corrected Cursor MCP tool advertisement to encode Cursor's `McpTools` wrapper schema, stopped injecting static `composer-2` defaults into successful live or cached-live catalogs, and added stable conversation ids plus same-stream MCP tool-result resume for Cursor tool turns.
15
+ - Hardened Cursor's private protocol edge by accepting raw UTF-8/JSON MCP argument bytes in addition to protobuf `Value` arguments, correlating historical tool results with their originating tool calls, preserving fast/thinking model modes as separate catalog groups, treating ambiguous effort-like suffixes such as `-max` as standalone model names without sibling evidence, and cancelling paused tool streams on abort or idle timeout.
16
+
17
+ ### Fixed
18
+
19
+ - Fixed Cursor tool-call continuations by background-pumping Run stream frames while Atomic executes tools, so request-context/KV/control frames are answered before the next public message is consumed and paused turns no longer appear frozen after the first tool call.
20
+ - Added bounded Cursor auth and transport request deadlines, cancelled paused streams when tool-result resume writes fail, and synchronized the Bun lockfile for the bundled Cursor package.
21
+ - Fixed Cursor stream timeout and lifecycle edge cases so per-request deadlines bound stream open/read/resume writes, timeout exits reset instead of gracefully closing streams, paused-turn abort/idle/replacement cleanup cannot leak or cancel a replacement turn, and non-MCP Cursor exec protocol messages are tolerated without ending the assistant turn ([#1286](https://github.com/bastani-inc/atomic/issues/1286)).
22
+ - Aligned Cursor Run request handling with the private Cursor CLI protocol by omitting the unsupported custom system-prompt field, serving conversation-state blobs through same-stream KV responses, returning MCP tool definitions from request-context responses, rejecting native Cursor execs so the model falls back to MCP tools, and pausing pending tool calls when Cursor waits for results without a terminal frame ([#1286](https://github.com/bastani-inc/atomic/issues/1286)).
23
+ - Fixed live Cursor models disappearing after CLI restart by rediscovering the live catalog on `session_start` from stored Cursor OAuth credentials when the cached catalog is missing or stale, so live-only models such as Composer 2.5 can be restored without requiring another login.
24
+
25
+ ### Security
26
+
27
+ - Cursor credentials are handled through Atomic OAuth storage only; Authorization headers, token-like diagnostics, and Cursor PKCE poll verifier/UUID values are redacted, and HTTP/2 is handled by the bundled native transport without a localhost proxy or Node subprocess.
@@ -0,0 +1,26 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bastani, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ Attribution: small implementation facts and endpoint/protocol notes were adapted
16
+ from the MIT-licensed pi-cursor-provider project by Netanel Draiman
17
+ (https://github.com/ndraiman/pi-cursor-provider). This package does not copy the
18
+ provider wholesale and does not include its local proxy implementation.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
@@ -0,0 +1,22 @@
1
+ # @bastani/cursor
2
+
3
+ First-party Atomic provider for Cursor subscription models.
4
+
5
+ ## Status
6
+
7
+ This package registers `cursor` via Atomic's bundled extension provider API. `/login` shows **Cursor** and stores credentials through Atomic OAuth storage (`~/.atomic/agent/auth.json`). The login URL and PKCE polling behavior intentionally match the MIT-licensed [`ndraiman/pi-cursor-provider`](https://github.com/ndraiman/pi-cursor-provider) reference: `callbacks.onAuth({ url: loginUrl })`, no extra login warning/instruction copy, and polling `api2.cursor.sh/auth/poll` until Cursor returns tokens.
8
+
9
+ The runtime protocol is also aligned to `ndraiman/pi-cursor-provider` at commit `82fc4e73f9ae820d87b34ac36713b18989910a36`: Atomic vendors the reference `cursor-models-raw.json` and generated `proto/agent_pb.ts`, and builds Cursor request/control messages through `@bufbuild/protobuf` descriptors instead of hand-maintained protobuf bytes. HTTP/2 itself is handled by the generated `@bastani/atomic-natives` Rust/N-API package rather than a separate local proxy.
10
+
11
+ The unavoidable Atomic-specific integration difference is the provider surface: Atomic exposes a native `cursor-agent` `streamSimple` provider instead of the reference package's localhost OpenAI-compatible proxy. The Cursor auth/model/protocol bytes should otherwise stay reference-derived.
12
+
13
+ ## Limitations
14
+
15
+ - Text input only. Vision/image content is rejected.
16
+ - Cursor's private API may change without notice.
17
+ - HTTP/2 transport requires the bundled `@bastani/atomic-natives` Rust/N-API native client for the current platform.
18
+ - Credentials are OAuth-only. Do not pass Cursor tokens via command-line args, environment variables, logs, or local proxy processes.
19
+
20
+ ## Attribution
21
+
22
+ Cursor auth, model fallback, and generated protobuf behavior are derived from the MIT-licensed `ndraiman/pi-cursor-provider` project.
@@ -0,0 +1,9 @@
1
+ export { default } from "./src/provider.js";
2
+ export { registerCursorProvider, type CursorProviderConfig, type CursorProviderHost, type CursorProviderRegistrationOptions, type CursorProviderRuntime } from "./src/provider.js";
3
+ export { CursorAuthError, CursorAuthService, CursorToken, createPkcePair, deriveCursorTokenExpiry, fromOAuthCredentials, redactOAuthCredentials, toOAuthCredentials } from "./src/auth.js";
4
+ export { CursorModelDiscoveryError, CursorModelDiscoveryService } from "./src/models.js";
5
+ export { FileCursorCatalogCache, getDefaultCursorCatalogCachePath, parseCursorCatalogCacheRecord, toCursorCatalogCacheRecord, type CursorCatalogCache } from "./src/catalog-cache.js";
6
+ export { createEstimatedCursorCatalog, insertEffortBeforeCursorSuffix, mapCursorCatalogToProviderModels, parseCursorVariant, resolveCursorModelVariant } from "./src/model-mapper.js";
7
+ export { CursorConversationStateStore } from "./src/conversation-state.js";
8
+ export { CursorStreamAdapter, createCursorStreamAdapter } from "./src/stream.js";
9
+ export { Http2CursorAgentTransport } from "./src/transport.js";
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@bastani/cursor",
3
+ "version": "0.8.29-alpha.2",
4
+ "private": true,
5
+ "description": "Experimental first-party Atomic extension for Cursor OAuth, model discovery, and streaming provider registration.",
6
+ "contributors": [
7
+ "Norin Lavaee",
8
+ "Alex Lavaee"
9
+ ],
10
+ "license": "MIT",
11
+ "type": "module",
12
+ "engines": {
13
+ "bun": ">=1.3.14"
14
+ },
15
+ "main": "./index.ts",
16
+ "types": "./index.ts",
17
+ "exports": {
18
+ ".": "./index.ts"
19
+ },
20
+ "files": [
21
+ "index.ts",
22
+ "src/**/*.ts",
23
+ "src/**/*.json",
24
+ "src/proto/README.md",
25
+ "README.md",
26
+ "CHANGELOG.md",
27
+ "LICENSE"
28
+ ],
29
+ "pi": {
30
+ "extensions": [
31
+ "./index.ts"
32
+ ]
33
+ },
34
+ "peerDependencies": {
35
+ "@bastani/atomic": "*"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "@bastani/atomic": {
39
+ "optional": true
40
+ }
41
+ },
42
+ "dependencies": {
43
+ "@bastani/atomic-natives": "0.8.29-alpha.2",
44
+ "@bufbuild/protobuf": "^2.0.0"
45
+ }
46
+ }
@@ -0,0 +1,352 @@
1
+ import type { OAuthCredentials, OAuthLoginCallbacks } from "@earendil-works/pi-ai";
2
+ import { createHash, randomBytes as nodeRandomBytes, randomUUID } from "node:crypto";
3
+ import {
4
+ CURSOR_API_BASE_URL,
5
+ CURSOR_AUTH_POLL_PATH,
6
+ CURSOR_DEFAULT_TOKEN_TTL_MS,
7
+ CURSOR_OAUTH_EXPIRY_SKEW_MS,
8
+ CURSOR_REFRESH_PATH,
9
+ CURSOR_WEB_BASE_URL,
10
+ CURSOR_LOGIN_PATH,
11
+ parseJsonObject,
12
+ readStringField,
13
+ readNumberField,
14
+ sanitizeDiagnosticText,
15
+ } from "./config.js";
16
+
17
+ export type CursorAuthErrorCode =
18
+ | "LoginCancelled"
19
+ | "PollTimedOut"
20
+ | "PollRejected"
21
+ | "RefreshTokenExpired"
22
+ | "CursorApiRejected"
23
+ | "NetworkError";
24
+
25
+ export class CursorAuthError extends Error {
26
+ constructor(
27
+ readonly code: CursorAuthErrorCode,
28
+ message: string,
29
+ readonly status?: number,
30
+ ) {
31
+ super(message);
32
+ this.name = "CursorAuthError";
33
+ }
34
+ }
35
+
36
+ export class CursorToken {
37
+ readonly kind: "access" | "refresh";
38
+ readonly #value: string;
39
+
40
+ constructor(kind: "access" | "refresh", value: string) {
41
+ this.kind = kind;
42
+ this.#value = value;
43
+ }
44
+
45
+ unwrap(): string {
46
+ return this.#value;
47
+ }
48
+
49
+ toString(): string {
50
+ return `[redacted cursor ${this.kind} token]`;
51
+ }
52
+
53
+ toJSON(): string {
54
+ return this.toString();
55
+ }
56
+ }
57
+
58
+ export interface CursorCredentialBundle {
59
+ readonly access: CursorToken;
60
+ readonly refresh: CursorToken;
61
+ readonly expires: number;
62
+ }
63
+
64
+ export interface CursorPkcePair {
65
+ readonly verifier: string;
66
+ readonly challenge: string;
67
+ }
68
+
69
+ export type CursorFetch = (url: string, init?: RequestInit) => Promise<Response>;
70
+ export type CursorSleep = (milliseconds: number, signal?: AbortSignal) => Promise<void>;
71
+ export type CursorUuid = () => string;
72
+ export type CursorRandomBytes = (length: number) => Uint8Array;
73
+ export type CursorNow = () => number;
74
+
75
+ export interface CursorAuthServiceOptions {
76
+ readonly fetch?: CursorFetch;
77
+ readonly sleep?: CursorSleep;
78
+ readonly uuid?: CursorUuid;
79
+ readonly randomBytes?: CursorRandomBytes;
80
+ readonly now?: CursorNow;
81
+ readonly maxPollAttempts?: number;
82
+ readonly initialPollDelayMs?: number;
83
+ readonly maxPollDelayMs?: number;
84
+ readonly pollBackoffMultiplier?: number;
85
+ readonly fetchTimeoutMs?: number;
86
+ readonly apiBaseUrl?: string;
87
+ readonly webBaseUrl?: string;
88
+ }
89
+
90
+ interface CursorTokenResponse {
91
+ readonly accessToken: string;
92
+ readonly refreshToken: string;
93
+ }
94
+
95
+ const DEFAULT_MAX_POLL_ATTEMPTS = 150;
96
+ const DEFAULT_INITIAL_POLL_DELAY_MS = 1000;
97
+ const DEFAULT_MAX_POLL_DELAY_MS = 10000;
98
+ const DEFAULT_POLL_BACKOFF_MULTIPLIER = 1.2;
99
+ const DEFAULT_FETCH_TIMEOUT_MS = 30_000;
100
+
101
+ export function base64Url(bytes: Uint8Array): string {
102
+ return Buffer.from(bytes).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/u, "");
103
+ }
104
+
105
+ export function createPkcePair(randomBytes: CursorRandomBytes = defaultRandomBytes): CursorPkcePair {
106
+ const verifier = base64Url(randomBytes(96));
107
+ const challenge = createHash("sha256").update(verifier).digest("base64url");
108
+ return { verifier, challenge };
109
+ }
110
+
111
+ export function toOAuthCredentials(bundle: CursorCredentialBundle): OAuthCredentials {
112
+ return {
113
+ access: bundle.access.unwrap(),
114
+ refresh: bundle.refresh.unwrap(),
115
+ expires: bundle.expires,
116
+ };
117
+ }
118
+
119
+ export function fromOAuthCredentials(credentials: OAuthCredentials): CursorCredentialBundle {
120
+ return {
121
+ access: new CursorToken("access", credentials.access),
122
+ refresh: new CursorToken("refresh", credentials.refresh),
123
+ expires: credentials.expires,
124
+ };
125
+ }
126
+
127
+ export function redactOAuthCredentials(credentials: OAuthCredentials): OAuthCredentials {
128
+ return {
129
+ access: "[redacted]",
130
+ refresh: "[redacted]",
131
+ expires: credentials.expires,
132
+ };
133
+ }
134
+
135
+ export function deriveCursorTokenExpiry(accessToken: string, now: CursorNow = Date.now): number {
136
+ const parts = accessToken.split(".");
137
+ if (parts.length < 2 || !parts[1]) {
138
+ return now() + CURSOR_DEFAULT_TOKEN_TTL_MS;
139
+ }
140
+
141
+ try {
142
+ const payload = Buffer.from(parts[1], "base64url").toString("utf8");
143
+ const parsed = parseJsonObject(payload);
144
+ if (!parsed) {
145
+ return now() + CURSOR_DEFAULT_TOKEN_TTL_MS;
146
+ }
147
+ const exp = readNumberField(parsed, "exp");
148
+ return exp ? exp * 1000 - CURSOR_OAUTH_EXPIRY_SKEW_MS : now() + CURSOR_DEFAULT_TOKEN_TTL_MS;
149
+ } catch {
150
+ return now() + CURSOR_DEFAULT_TOKEN_TTL_MS;
151
+ }
152
+ }
153
+
154
+ export class CursorAuthService {
155
+ readonly #fetch: CursorFetch;
156
+ readonly #sleep: CursorSleep;
157
+ readonly #uuid: CursorUuid;
158
+ readonly #randomBytes: CursorRandomBytes;
159
+ readonly #now: CursorNow;
160
+ readonly #maxPollAttempts: number;
161
+ readonly #initialPollDelayMs: number;
162
+ readonly #maxPollDelayMs: number;
163
+ readonly #pollBackoffMultiplier: number;
164
+ readonly #fetchTimeoutMs: number;
165
+ readonly #apiBaseUrl: string;
166
+ readonly #webBaseUrl: string;
167
+
168
+ constructor(options: CursorAuthServiceOptions = {}) {
169
+ this.#fetch = options.fetch ?? fetch;
170
+ this.#sleep = options.sleep ?? sleep;
171
+ this.#uuid = options.uuid ?? randomUUID;
172
+ this.#randomBytes = options.randomBytes ?? defaultRandomBytes;
173
+ this.#now = options.now ?? Date.now;
174
+ this.#maxPollAttempts = options.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS;
175
+ this.#initialPollDelayMs = options.initialPollDelayMs ?? DEFAULT_INITIAL_POLL_DELAY_MS;
176
+ this.#maxPollDelayMs = options.maxPollDelayMs ?? DEFAULT_MAX_POLL_DELAY_MS;
177
+ this.#pollBackoffMultiplier = options.pollBackoffMultiplier ?? DEFAULT_POLL_BACKOFF_MULTIPLIER;
178
+ this.#fetchTimeoutMs = options.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
179
+ this.#apiBaseUrl = options.apiBaseUrl ?? CURSOR_API_BASE_URL;
180
+ this.#webBaseUrl = options.webBaseUrl ?? CURSOR_WEB_BASE_URL;
181
+ }
182
+
183
+ async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
184
+ const bundle = await this.loginCursor(callbacks);
185
+ return toOAuthCredentials(bundle);
186
+ }
187
+
188
+ async loginCursor(callbacks: OAuthLoginCallbacks): Promise<CursorCredentialBundle> {
189
+ const { verifier, challenge } = createPkcePair(this.#randomBytes);
190
+ const uuid = this.#uuid();
191
+ const loginUrl = new URL(CURSOR_LOGIN_PATH, this.#webBaseUrl);
192
+ loginUrl.searchParams.set("challenge", challenge);
193
+ loginUrl.searchParams.set("uuid", uuid);
194
+ loginUrl.searchParams.set("mode", "login");
195
+ loginUrl.searchParams.set("redirectTarget", "cli");
196
+ callbacks.onAuth({ url: loginUrl.toString() });
197
+
198
+ let delayMs = this.#initialPollDelayMs;
199
+ let consecutiveErrors = 0;
200
+ for (let attempt = 0; attempt < this.#maxPollAttempts; attempt += 1) {
201
+ await this.#sleep(delayMs, callbacks.signal);
202
+ if (callbacks.signal?.aborted) {
203
+ throw new CursorAuthError("LoginCancelled", "Cursor login was cancelled.");
204
+ }
205
+
206
+ const pollUrl = new URL(CURSOR_AUTH_POLL_PATH, this.#apiBaseUrl);
207
+ pollUrl.searchParams.set("uuid", uuid);
208
+ pollUrl.searchParams.set("verifier", verifier);
209
+
210
+ let response: Response;
211
+ try {
212
+ response = await this.fetchWithDeadline(pollUrl.toString(), { method: "GET" }, callbacks.signal);
213
+ } catch {
214
+ if (callbacks.signal?.aborted) throw new CursorAuthError("LoginCancelled", "Cursor login was cancelled.");
215
+ consecutiveErrors += 1;
216
+ if (consecutiveErrors >= 3) {
217
+ throw new CursorAuthError("NetworkError", "Cursor login polling failed after repeated network errors.");
218
+ }
219
+ delayMs = Math.min(this.#maxPollDelayMs, Math.ceil(delayMs * this.#pollBackoffMultiplier));
220
+ continue;
221
+ }
222
+
223
+ if (response.status === 404) {
224
+ consecutiveErrors = 0;
225
+ delayMs = Math.min(this.#maxPollDelayMs, Math.ceil(delayMs * this.#pollBackoffMultiplier));
226
+ continue;
227
+ }
228
+
229
+ if (!response.ok) {
230
+ consecutiveErrors += 1;
231
+ if (consecutiveErrors >= 3) {
232
+ const responseText = await response.text();
233
+ throw new CursorAuthError(
234
+ "PollRejected",
235
+ `Cursor login polling was rejected (${response.status}): ${sanitizeDiagnosticText(responseText, [verifier, uuid, pollUrl.toString()])}`,
236
+ response.status,
237
+ );
238
+ }
239
+ delayMs = Math.min(this.#maxPollDelayMs, Math.ceil(delayMs * this.#pollBackoffMultiplier));
240
+ continue;
241
+ }
242
+
243
+ const tokenResponse = parseTokenResponse(await response.text());
244
+ if (!tokenResponse || tokenResponse.refreshToken.length === 0) {
245
+ throw new CursorAuthError("PollRejected", "Cursor login response did not include usable tokens.", response.status);
246
+ }
247
+
248
+ return {
249
+ access: new CursorToken("access", tokenResponse.accessToken),
250
+ refresh: new CursorToken("refresh", tokenResponse.refreshToken),
251
+ expires: deriveCursorTokenExpiry(tokenResponse.accessToken, this.#now),
252
+ };
253
+ }
254
+
255
+ throw new CursorAuthError("PollTimedOut", "Cursor login timed out before authorization completed.");
256
+ }
257
+
258
+ async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {
259
+ const bundle = await this.refreshCursorCredentials(fromOAuthCredentials(credentials));
260
+ return toOAuthCredentials(bundle);
261
+ }
262
+
263
+ async refreshCursorCredentials(credentials: CursorCredentialBundle): Promise<CursorCredentialBundle> {
264
+ const refresh = credentials.refresh.unwrap();
265
+ let response: Response;
266
+ try {
267
+ response = await this.fetchWithDeadline(new URL(CURSOR_REFRESH_PATH, this.#apiBaseUrl).toString(), {
268
+ method: "POST",
269
+ headers: {
270
+ Authorization: `Bearer ${refresh}`,
271
+ "Content-Type": "application/json",
272
+ },
273
+ body: "{}",
274
+ });
275
+ } catch {
276
+ throw new CursorAuthError("NetworkError", "Cursor token refresh failed because the network request failed.");
277
+ }
278
+
279
+ if (response.status === 401 || response.status === 403) {
280
+ throw new CursorAuthError("RefreshTokenExpired", "Cursor refresh token expired or was rejected; run /login again.", response.status);
281
+ }
282
+ if (!response.ok) {
283
+ const responseText = await response.text();
284
+ throw new CursorAuthError(
285
+ "CursorApiRejected",
286
+ `Cursor token refresh failed (${response.status}): ${sanitizeDiagnosticText(responseText, [refresh])}`,
287
+ response.status,
288
+ );
289
+ }
290
+
291
+ const tokenResponse = parseTokenResponse(await response.text());
292
+ if (!tokenResponse) {
293
+ throw new CursorAuthError("CursorApiRejected", "Cursor token refresh response did not include an access token.", response.status);
294
+ }
295
+ return {
296
+ access: new CursorToken("access", tokenResponse.accessToken),
297
+ refresh: new CursorToken("refresh", tokenResponse.refreshToken || refresh),
298
+ expires: deriveCursorTokenExpiry(tokenResponse.accessToken, this.#now),
299
+ };
300
+ }
301
+
302
+ private async fetchWithDeadline(url: string, init: RequestInit, parentSignal?: AbortSignal): Promise<Response> {
303
+ const controller = new AbortController();
304
+ let timeout: ReturnType<typeof setTimeout> | undefined;
305
+ const timeoutPromise = new Promise<never>((_, reject) => {
306
+ timeout = setTimeout(() => {
307
+ controller.abort();
308
+ reject(new CursorAuthError("NetworkError", "Cursor authentication request timed out."));
309
+ }, this.#fetchTimeoutMs);
310
+ timeout.unref?.();
311
+ });
312
+ const onAbort = (): void => controller.abort();
313
+ parentSignal?.addEventListener("abort", onAbort, { once: true });
314
+ try {
315
+ return await Promise.race([this.#fetch(url, { ...init, signal: controller.signal }), timeoutPromise]);
316
+ } finally {
317
+ if (timeout) clearTimeout(timeout);
318
+ parentSignal?.removeEventListener("abort", onAbort);
319
+ }
320
+ }
321
+ }
322
+
323
+ function parseTokenResponse(text: string): CursorTokenResponse | undefined {
324
+ const parsed = parseJsonObject(text);
325
+ if (!parsed) return undefined;
326
+ const accessToken = readStringField(parsed, "accessToken") ?? readStringField(parsed, "access_token");
327
+ const refreshToken = readStringField(parsed, "refreshToken") ?? readStringField(parsed, "refresh_token") ?? "";
328
+ if (!accessToken) return undefined;
329
+ return { accessToken, refreshToken };
330
+ }
331
+
332
+ function defaultRandomBytes(length: number): Uint8Array {
333
+ return nodeRandomBytes(length);
334
+ }
335
+
336
+ function sleep(milliseconds: number, signal?: AbortSignal): Promise<void> {
337
+ if (signal?.aborted) {
338
+ return Promise.reject(new CursorAuthError("LoginCancelled", "Cursor login was cancelled."));
339
+ }
340
+ return new Promise((resolve, reject) => {
341
+ const abort = (): void => {
342
+ clearTimeout(timer);
343
+ signal?.removeEventListener("abort", abort);
344
+ reject(new CursorAuthError("LoginCancelled", "Cursor login was cancelled."));
345
+ };
346
+ const timer = setTimeout(() => {
347
+ signal?.removeEventListener("abort", abort);
348
+ resolve();
349
+ }, milliseconds);
350
+ signal?.addEventListener("abort", abort, { once: true });
351
+ });
352
+ }