@rahularya01/pi-cursor 1.1.0 → 1.2.1

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 CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.2.1] - 2026-07-23
4
+
5
+ ### Fixed
6
+
7
+ - Stream idle watchdog no longer treats long pure-reasoning turns as dead: `tokenDelta`, handled native-tool reject round-trips, and `toolCallCompleted` now count as progress.
8
+ - **Idle timeouts and silent retries are disabled by default** (`PI_CURSOR_STREAM_IDLE_TIMEOUT_MS=0`, `PI_CURSOR_RESUME_IDLE_TIMEOUT_MS=0`, `PI_CURSOR_STREAM_IDLE_MAX_RETRIES=0`, `PI_CURSOR_H2_IDLE_TIMEOUT_MS=0`) so agent turns can run as long as Cursor keeps the stream open. Re-enable via env if you want a safety net.
9
+ - h2-bridge activity kill is off by default and configurable (`PI_CURSOR_H2_*_TIMEOUT_MS`); parent heartbeats still reset it when enabled.
10
+ - Blind idle retries (when re-enabled) are skipped if partial text/thinking was already streamed (avoids duplicated/jumbled answers).
11
+ - Idle retries force-refresh access tokens when a token provider is available.
12
+ - Conversation blob stores are soft-capped (~128 MiB) to limit long-session memory growth.
13
+ - Tool result `isError` is propagated into Cursor MCP results.
14
+ - Context-mode side-channel detection covers additional compaction / `[context]` injections.
15
+
16
+ ### Added
17
+
18
+ - `/cursor.doctor` surfaces `lastStreamEvent`, last idle timeout metadata, and configured idle timeouts.
19
+ - Documented stream/bridge idle env vars in README.
20
+ - Unit coverage for idle progress classification, blind-restart gating, blob trimming, and timeout resolvers.
21
+
22
+ ## [1.2.0] - 2026-07-23
23
+
24
+ ### Changed
25
+
26
+ - Package now ships a bundled, minified `dist/` build (via `tsup`) instead of raw TypeScript source. Unpacked package size dropped ~818 KB → ~222 KB (packed ~154 KB → ~69 KB) by tree-shaking the generated protobuf module (~1000 exports, ~70 used). `main` and `pi.extensions` now point at `./dist/index.js`.
27
+ - `prepare` script builds `dist/` on install, so `git:`-based installs work without a committed build.
28
+
29
+ ### Fixed
30
+
31
+ - Streaming hot path: the thinking-tag filter regex is compiled once at module load instead of being rebuilt on every streamed chunk.
32
+
3
33
  ## [1.1.0] - 2026-07-23
4
34
 
5
35
  ### Added
package/README.md CHANGED
@@ -140,16 +140,23 @@ Pi Coding Agent → streamSimple (cursor-native)
140
140
 
141
141
  ## Configuration
142
142
 
143
- | Variable | Purpose |
144
- | ------------------------------------------ | ------------------------------------------------------------------------ |
145
- | `PI_CURSOR_AGENT_URL` / `CURSOR_AGENT_URL` | Override agent base URL (default: `https://agentn.us.api5.cursor.sh`). |
146
- | `CURSOR_ACCESS_TOKEN` | Static access token override. |
147
- | `PI_CURSOR_CLIENT_VERSION` | Pin `x-cursor-client-version` header sent by the HTTP/2 bridge. |
148
- | `PI_CURSOR_SYSTEM_CREDENTIALS` | `0`/`false` to disable Keychain/IDE credential reuse (default: allow). |
149
- | `PI_CURSOR_RAW_MODELS` | Disable effort-suffix model collapse. |
150
- | `PI_CURSOR_PROVIDER_DEBUG` | Enable JSONL debug logging (`/tmp/pi-cursor-debug.jsonl`). |
151
- | `CURSOR_USAGE_SESSION_TOKEN` | Optional `WorkosCursorSessionToken` fallback cookie for `/cursor.usage`. |
152
- | `PI_OFFLINE` | Skip live model discovery on startup. |
143
+ | Variable | Purpose |
144
+ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
145
+ | `PI_CURSOR_AGENT_URL` / `CURSOR_AGENT_URL` | Override agent base URL (default: `https://agentn.us.api5.cursor.sh`). |
146
+ | `CURSOR_ACCESS_TOKEN` | Static access token override. |
147
+ | `PI_CURSOR_CLIENT_VERSION` | Pin `x-cursor-client-version` header sent by the HTTP/2 bridge. |
148
+ | `PI_CURSOR_SYSTEM_CREDENTIALS` | `0`/`false` to disable Keychain/IDE credential reuse (default: allow). |
149
+ | `PI_CURSOR_RAW_MODELS` | Disable effort-suffix model collapse. |
150
+ | `PI_CURSOR_PROVIDER_DEBUG` | Enable JSONL debug logging (`/tmp/pi-cursor-debug.jsonl`). |
151
+ | `CURSOR_USAGE_SESSION_TOKEN` | Optional `WorkosCursorSessionToken` fallback cookie for `/cursor.usage`. |
152
+ | `PI_OFFLINE` | Skip live model discovery on startup. |
153
+ | `PI_CURSOR_STREAM_IDLE_TIMEOUT_MS` | Stream idle watchdog. **Default `0` (disabled)** so turns can run unbounded. Set e.g. `600000` to re-enable. |
154
+ | `PI_CURSOR_RESUME_IDLE_TIMEOUT_MS` | Idle timeout after tool-result resume. **Default `0` (disabled)**. |
155
+ | `PI_CURSOR_STREAM_IDLE_MAX_RETRIES` | Silent full-request retries after idle. **Default `0` (disabled)**. |
156
+ | `PI_CURSOR_ACTIVE_BRIDGE_TTL_MS` | How long a mid-tool bridge stays parked waiting for tool results (default: 1 hour). |
157
+ | `PI_CURSOR_H2_CONNECT_TIMEOUT_MS` | h2-bridge initial connect kill (default: `30000`; `0` disables). |
158
+ | `PI_CURSOR_H2_IDLE_TIMEOUT_MS` | h2-bridge activity idle kill. **Default `0` (disabled)**. Parent heartbeats reset it when enabled. |
159
+ | `PI_CURSOR_MIDPAUSE_REBUILD_MAX_AGE_MS` | Max age of mid-pause metadata used for full-history rebuild (default: 15 min). |
153
160
 
154
161
  ## Architecture notes
155
162
 
@@ -170,8 +177,9 @@ The OpenAI-compatible local proxy remains **internal/quarantined** (not exported
170
177
 
171
178
  ## Troubleshooting
172
179
 
173
- - **Not logged in / 401:** Ensure Cursor CLI or app is logged in, or run `/login cursor` again. Check `/cursor.doctor` to verify your `tokenSource`. Tokens from CLI/IDE are re-resolved when near expiry.
180
+ - **Not logged in / 401:** Ensure Cursor CLI or app is logged in, or run `/login cursor` again. Check `/cursor.doctor` to verify your `tokenSource`. Tokens from CLI/IDE are re-resolved when near expiry; idle stream retries also force-refresh credentials.
174
181
  - **Empty / hung stream:** Cursor may have updated wire headers; verify network connectivity or bump `PI_CURSOR_CLIENT_VERSION`. `/cursor.doctor` prints the active `clientVersion`.
182
+ - **Stuck / dies after a few minutes of work:** Idle watchdogs are **off by default**. If you re-enabled them via env, set `PI_CURSOR_STREAM_IDLE_TIMEOUT_MS=0` and `PI_CURSOR_H2_IDLE_TIMEOUT_MS=0`. Check `/cursor.doctor` for `lastIdleTimeoutAt` / `lastStreamEvent`. Enable `PI_CURSOR_PROVIDER_DEBUG=1` and inspect `/tmp/pi-cursor-debug.jsonl`.
175
183
  - **Tool continuation lost:** The provider now prefers full-history rebuild when checkpoints are stale/mismatched. If recovery still skips, `/cursor.doctor` shows `lastRecoverySkipReason`. Retry the turn or start a new chat.
176
184
  - **WSL credential detection:** Ensure your Windows user profile folder exists under `/mnt/c/Users/` and is readable from WSL. Disable with `PI_CURSOR_SYSTEM_CREDENTIALS=0` if undesired.
177
185
 
@@ -95,28 +95,66 @@ async function readMessage() {
95
95
  const configBuf = await readMessage();
96
96
  if (!configBuf) process.exit(1);
97
97
 
98
- const config = JSON.parse(configBuf.toString("utf8"));
98
+ let config;
99
+ try {
100
+ config = JSON.parse(configBuf.toString("utf8"));
101
+ } catch {
102
+ process.stderr.write("[h2-bridge] invalid config JSON\n");
103
+ process.exit(1);
104
+ }
105
+ if (!config || typeof config !== "object") {
106
+ process.stderr.write("[h2-bridge] config must be a JSON object\n");
107
+ process.exit(1);
108
+ }
99
109
  const { accessToken, url, path: rpcPath, unary } = config;
110
+ // Connect timeout still protects against a hung first handshake (default 30s).
111
+ // Activity idle is off by default (0) so long agent turns are not killed;
112
+ // set idleTimeoutMs / PI_CURSOR_H2_IDLE_TIMEOUT_MS to re-enable a safety net.
113
+ // Parent heartbeats every 5s also reset the timer when it is enabled.
114
+ const connectTimeoutMs = optionalMs(config.connectTimeoutMs, 30_000);
115
+ const idleTimeoutMs = optionalMs(config.idleTimeoutMs, 0);
116
+
117
+ /** Parse ms; 0 means disabled. Invalid/missing uses fallback. */
118
+ function optionalMs(value, fallback) {
119
+ if (value === undefined || value === null || value === "") return fallback;
120
+ const n = Number(value);
121
+ if (!Number.isFinite(n) || n < 0) return fallback;
122
+ if (n === 0) return 0;
123
+ return Math.floor(n);
124
+ }
100
125
 
101
126
  const client = http2.connect(url || "https://api2.cursor.sh");
102
127
 
103
- // Guard against initial connection failure. Reset on any h2 activity
104
- // so long-running agent conversations (with tool call round-trips) survive.
105
- let timeout = setTimeout(killBridge, 30_000);
128
+ // Optional watchdog: connect timeout until first activity, then activity idle.
129
+ let timeout = undefined;
130
+
131
+ function clearBridgeTimeout() {
132
+ if (timeout) clearTimeout(timeout);
133
+ timeout = undefined;
134
+ }
135
+
136
+ function armBridgeTimeout(ms) {
137
+ clearBridgeTimeout();
138
+ if (!ms || ms <= 0) return;
139
+ timeout = setTimeout(killBridge, ms);
140
+ }
106
141
 
107
142
  function resetTimeout() {
108
- clearTimeout(timeout);
109
- timeout = setTimeout(killBridge, 120_000);
143
+ // After first I/O, only the activity idle (if enabled) applies.
144
+ armBridgeTimeout(idleTimeoutMs);
110
145
  }
111
146
 
112
147
  function killBridge() {
113
- clearTimeout(timeout);
148
+ clearBridgeTimeout();
114
149
  client.destroy();
115
150
  process.exit(1);
116
151
  }
117
152
 
153
+ // Initial connect guard only (skipped when connectTimeoutMs is 0).
154
+ armBridgeTimeout(connectTimeoutMs);
155
+
118
156
  client.on("error", () => {
119
- clearTimeout(timeout);
157
+ clearBridgeTimeout();
120
158
  process.exit(1);
121
159
  });
122
160
 
@@ -156,7 +194,7 @@ h2Stream.on("data", (chunk) => {
156
194
  });
157
195
 
158
196
  h2Stream.on("end", () => {
159
- clearTimeout(timeout);
197
+ clearBridgeTimeout();
160
198
  client.close();
161
199
  if (isErrorStatus()) {
162
200
  const body = Buffer.concat(errorChunks).toString("utf8").trim();
@@ -172,7 +210,7 @@ h2Stream.on("end", () => {
172
210
  });
173
211
 
174
212
  h2Stream.on("error", () => {
175
- clearTimeout(timeout);
213
+ clearBridgeTimeout();
176
214
  client.close();
177
215
  process.exit(1);
178
216
  });