@rahularya01/pi-cursor 1.2.1 → 1.2.3
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 +20 -0
- package/README.md +19 -18
- package/dist/h2-bridge.mjs +40 -2
- package/dist/index.js +17 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.2.3] - 2026-07-24
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Permanent hang guard.** A stream that receives no upstream progress of any kind now recovers/retries or ends the turn with a clear error instead of parking forever. Since 1.2.1 disabled the idle watchdog by default, any un-answered exec or silent/dropped upstream left the run "stuck on working" indefinitely (observed: a turn parked ~26 min until manually aborted). The watchdog is re-enabled by default as a **silence** guard: `PI_CURSOR_STREAM_IDLE_TIMEOUT_MS` / `PI_CURSOR_RESUME_IDLE_TIMEOUT_MS` default to `120000` (2 min) and `PI_CURSOR_STREAM_IDLE_MAX_RETRIES` to `2`. Every server signal (text/thinking/token deltas, tool-call events, thinkingCompleted, heartbeat, summary, answered interaction/exec) counts as progress and resets it, and it is paused during tool execution — so long reasoning turns and slow tools are unaffected; it only fires on a genuine park. Set the env vars to `0` to restore the previous unbounded behavior.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `execServerMessage` handling is now recorded in the lifecycle log (`exec_server {execCase, handled}` for non-tool execs) and an unanswered exec sets `lastStreamEvent=exec_unanswered:<case>`. Previously exec messages were invisible in the lifecycle log — the blind spot behind unexplained mid-run stalls.
|
|
12
|
+
|
|
13
|
+
## [1.2.2] - 2026-07-23
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **Root hang:** Cursor `InteractionQuery` messages (web search, Exa, ask-question, switch-mode, create-plan, WebFetch field #9, unknown fields) are now always answered. Previously only WebFetch field #9 was handled — any other permission/query left the AgentService stream parked forever ("stops after a few minutes").
|
|
18
|
+
- Always-on lifecycle log at `$TMPDIR/pi-cursor-lifecycle.jsonl` (override with `PI_CURSOR_LIFECYCLE_LOG`) for stream start/close and interaction handling.
|
|
19
|
+
- h2-bridge: HTTP/2 PING every 20s to prevent intermediary idle GOAWAY; stderr/errors are surfaced instead of swallowed.
|
|
20
|
+
- Parent bridge now captures child stderr; heartbeats stay referenced during long tool pauses.
|
|
21
|
+
- Treat `heartbeat` / tool-call start / thinking-completed / summary updates as stream progress.
|
|
22
|
+
|
|
3
23
|
## [1.2.1] - 2026-07-23
|
|
4
24
|
|
|
5
25
|
### Fixed
|
package/README.md
CHANGED
|
@@ -140,23 +140,24 @@ 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
|
|
151
|
-
| `
|
|
152
|
-
| `
|
|
153
|
-
| `
|
|
154
|
-
| `
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
157
|
-
| `
|
|
158
|
-
| `
|
|
159
|
-
| `
|
|
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 verbose JSONL debug logging. |
|
|
151
|
+
| `PI_CURSOR_LIFECYCLE_LOG` | Always-on compact lifecycle log path (default: `$TMPDIR/pi-cursor-lifecycle.jsonl`). |
|
|
152
|
+
| `CURSOR_USAGE_SESSION_TOKEN` | Optional `WorkosCursorSessionToken` fallback cookie for `/cursor.usage`. |
|
|
153
|
+
| `PI_OFFLINE` | Skip live model discovery on startup. |
|
|
154
|
+
| `PI_CURSOR_STREAM_IDLE_TIMEOUT_MS` | Silence safety net: ms with **no upstream progress of any kind** before recover/retry/error. **Default `120000` (2 min)**; `0` disables (turns run unbounded). Any server signal resets it and it is paused during tool execution, so long reasoning/tools are unaffected — it only fires on a genuine park. |
|
|
155
|
+
| `PI_CURSOR_RESUME_IDLE_TIMEOUT_MS` | Same silence safety net after tool-result resume. **Default `120000` (2 min)**; `0` disables. |
|
|
156
|
+
| `PI_CURSOR_STREAM_IDLE_MAX_RETRIES` | Auto-recovery attempts after a silence timeout before erroring (skipped once any text/thinking was streamed, to avoid duplicate output). **Default `2`**; `0` disables. |
|
|
157
|
+
| `PI_CURSOR_ACTIVE_BRIDGE_TTL_MS` | How long a mid-tool bridge stays parked waiting for tool results (default: 1 hour). |
|
|
158
|
+
| `PI_CURSOR_H2_CONNECT_TIMEOUT_MS` | h2-bridge initial connect kill (default: `30000`; `0` disables). |
|
|
159
|
+
| `PI_CURSOR_H2_IDLE_TIMEOUT_MS` | h2-bridge activity idle kill. **Default `0` (disabled)**. Parent heartbeats reset it when enabled. |
|
|
160
|
+
| `PI_CURSOR_MIDPAUSE_REBUILD_MAX_AGE_MS` | Max age of mid-pause metadata used for full-history rebuild (default: 15 min). |
|
|
160
161
|
|
|
161
162
|
## Architecture notes
|
|
162
163
|
|
|
@@ -179,7 +180,7 @@ The OpenAI-compatible local proxy remains **internal/quarantined** (not exported
|
|
|
179
180
|
|
|
180
181
|
- **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.
|
|
181
182
|
- **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:**
|
|
183
|
+
- **Stuck / dies after a few minutes of work:** v1.2.2 answers all Cursor `InteractionQuery` permission prompts (web search / ask-question / etc.) that previously parked the stream. Inspect `$TMPDIR/pi-cursor-lifecycle.jsonl` for `interaction_query` / `bridge_close` events, and `/cursor.doctor` for `lastStreamEvent`. Full debug: `PI_CURSOR_PROVIDER_DEBUG=1`.
|
|
183
184
|
- **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.
|
|
184
185
|
- **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.
|
|
185
186
|
|
package/dist/h2-bridge.mjs
CHANGED
|
@@ -125,6 +125,28 @@ function optionalMs(value, fallback) {
|
|
|
125
125
|
|
|
126
126
|
const client = http2.connect(url || "https://api2.cursor.sh");
|
|
127
127
|
|
|
128
|
+
// HTTP/2 PING keeps intermediary/load-balancer sessions alive during long pure-thinking
|
|
129
|
+
// stretches where no DATA frames flow. Cursor may otherwise GOAWAY the stream mid-turn.
|
|
130
|
+
const pingEveryMs = optionalMs(config.pingIntervalMs, 20_000);
|
|
131
|
+
let pingTimer = undefined;
|
|
132
|
+
if (pingEveryMs > 0) {
|
|
133
|
+
pingTimer = setInterval(() => {
|
|
134
|
+
if (client.destroyed || client.closed) return;
|
|
135
|
+
try {
|
|
136
|
+
client.ping((err) => {
|
|
137
|
+
if (err) {
|
|
138
|
+
process.stderr.write(`[h2-bridge] ping failed: ${err.message}\n`);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
} catch (err) {
|
|
142
|
+
process.stderr.write(
|
|
143
|
+
`[h2-bridge] ping threw: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}, pingEveryMs);
|
|
147
|
+
pingTimer.unref?.();
|
|
148
|
+
}
|
|
149
|
+
|
|
128
150
|
// Optional watchdog: connect timeout until first activity, then activity idle.
|
|
129
151
|
let timeout = undefined;
|
|
130
152
|
|
|
@@ -146,6 +168,7 @@ function resetTimeout() {
|
|
|
146
168
|
|
|
147
169
|
function killBridge() {
|
|
148
170
|
clearBridgeTimeout();
|
|
171
|
+
if (pingTimer) clearInterval(pingTimer);
|
|
149
172
|
client.destroy();
|
|
150
173
|
process.exit(1);
|
|
151
174
|
}
|
|
@@ -153,11 +176,21 @@ function killBridge() {
|
|
|
153
176
|
// Initial connect guard only (skipped when connectTimeoutMs is 0).
|
|
154
177
|
armBridgeTimeout(connectTimeoutMs);
|
|
155
178
|
|
|
156
|
-
client.on("error", () => {
|
|
179
|
+
client.on("error", (err) => {
|
|
157
180
|
clearBridgeTimeout();
|
|
181
|
+
if (pingTimer) clearInterval(pingTimer);
|
|
182
|
+
process.stderr.write(
|
|
183
|
+
`[h2-bridge] client error: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
184
|
+
);
|
|
158
185
|
process.exit(1);
|
|
159
186
|
});
|
|
160
187
|
|
|
188
|
+
client.on("goaway", (errorCode, _lastStreamId, opaqueData) => {
|
|
189
|
+
process.stderr.write(
|
|
190
|
+
`[h2-bridge] GOAWAY errorCode=${errorCode} opaque=${opaqueData ? opaqueData.toString("utf8").slice(0, 200) : ""}\n`,
|
|
191
|
+
);
|
|
192
|
+
});
|
|
193
|
+
|
|
161
194
|
const headers = {
|
|
162
195
|
":method": "POST",
|
|
163
196
|
":path": rpcPath || "/agent.v1.AgentService/Run",
|
|
@@ -195,6 +228,7 @@ h2Stream.on("data", (chunk) => {
|
|
|
195
228
|
|
|
196
229
|
h2Stream.on("end", () => {
|
|
197
230
|
clearBridgeTimeout();
|
|
231
|
+
if (pingTimer) clearInterval(pingTimer);
|
|
198
232
|
client.close();
|
|
199
233
|
if (isErrorStatus()) {
|
|
200
234
|
const body = Buffer.concat(errorChunks).toString("utf8").trim();
|
|
@@ -209,8 +243,12 @@ h2Stream.on("end", () => {
|
|
|
209
243
|
setTimeout(() => process.exit(0), 100);
|
|
210
244
|
});
|
|
211
245
|
|
|
212
|
-
h2Stream.on("error", () => {
|
|
246
|
+
h2Stream.on("error", (err) => {
|
|
213
247
|
clearBridgeTimeout();
|
|
248
|
+
if (pingTimer) clearInterval(pingTimer);
|
|
249
|
+
process.stderr.write(
|
|
250
|
+
`[h2-bridge] stream error: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
251
|
+
);
|
|
214
252
|
client.close();
|
|
215
253
|
process.exit(1);
|
|
216
254
|
});
|