@herbertgao/pi-extensions 2026.9.9 → 2026.9.11

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 (170) hide show
  1. package/README.md +1 -0
  2. package/THIRD_PARTY_NOTICES.md +26 -0
  3. package/node_modules/@herbertgao/pi-bark/package.json +2 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
  5. package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
  6. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -2
  7. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +12 -0
  8. package/node_modules/@herbertgao/pi-subagents/README.md +427 -120
  9. package/node_modules/@herbertgao/pi-subagents/docs/rpc.md +184 -0
  10. package/node_modules/@herbertgao/pi-subagents/docs/workflows.md +466 -0
  11. package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +6 -6
  12. package/node_modules/@herbertgao/pi-subagents/examples/workflows/compose.js +52 -0
  13. package/node_modules/@herbertgao/pi-subagents/examples/workflows/fan-out-audit.js +56 -0
  14. package/node_modules/@herbertgao/pi-subagents/examples/workflows/gated-fix.js +60 -0
  15. package/node_modules/@herbertgao/pi-subagents/examples/workflows/lib/count-child.js +30 -0
  16. package/node_modules/@herbertgao/pi-subagents/examples/workflows/review-panel.js +68 -0
  17. package/node_modules/@herbertgao/pi-subagents/examples/workflows/structured-findings.js +81 -0
  18. package/node_modules/@herbertgao/pi-subagents/package.json +12 -10
  19. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +52 -12
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +837 -146
  21. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +213 -39
  22. package/node_modules/@herbertgao/pi-subagents/src/cross-extension-rpc.ts +73 -14
  23. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +101 -47
  24. package/node_modules/@herbertgao/pi-subagents/src/index.ts +2249 -914
  25. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +13 -0
  26. package/node_modules/@herbertgao/pi-subagents/src/mention-clone.ts +215 -0
  27. package/node_modules/@herbertgao/pi-subagents/src/mention.ts +147 -0
  28. package/node_modules/@herbertgao/pi-subagents/src/model-resolver.ts +9 -1
  29. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +40 -26
  30. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +18 -8
  31. package/node_modules/@herbertgao/pi-subagents/src/prompts.ts +46 -9
  32. package/node_modules/@herbertgao/pi-subagents/src/schedule.ts +21 -16
  33. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +137 -7
  34. package/node_modules/@herbertgao/pi-subagents/src/structured-output.ts +136 -0
  35. package/node_modules/@herbertgao/pi-subagents/src/types.ts +126 -8
  36. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-mention.ts +274 -0
  37. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +20 -5
  38. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +10 -4
  39. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +167 -22
  40. package/node_modules/@herbertgao/pi-subagents/src/ui/workflow-card.ts +555 -0
  41. package/node_modules/@herbertgao/pi-subagents/src/ui/workflow-dialog.ts +1304 -0
  42. package/node_modules/@herbertgao/pi-subagents/src/ui/workflow-menu.ts +226 -0
  43. package/node_modules/@herbertgao/pi-subagents/src/workflow/collisions.ts +122 -0
  44. package/node_modules/@herbertgao/pi-subagents/src/workflow/entry.ts +47 -0
  45. package/node_modules/@herbertgao/pi-subagents/src/workflow/host.ts +463 -0
  46. package/node_modules/@herbertgao/pi-subagents/src/workflow/journal.ts +164 -0
  47. package/node_modules/@herbertgao/pi-subagents/src/workflow/json-schema.ts +142 -0
  48. package/node_modules/@herbertgao/pi-subagents/src/workflow/meta.ts +401 -0
  49. package/node_modules/@herbertgao/pi-subagents/src/workflow/progress.ts +622 -0
  50. package/node_modules/@herbertgao/pi-subagents/src/workflow/runtime.ts +1399 -0
  51. package/node_modules/@herbertgao/pi-subagents/src/workflow/saved.ts +230 -0
  52. package/node_modules/@herbertgao/pi-subagents/src/workflow/task.ts +333 -0
  53. package/node_modules/@herbertgao/pi-subagents/src/workflow/tool-description.ts +200 -0
  54. package/node_modules/@herbertgao/pi-subagents/src/workflow/worker-source.ts +781 -0
  55. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +97 -95
  56. package/node_modules/@herbertgao/pi-subagents/src/xml.ts +13 -0
  57. package/node_modules/@herbertgao/resume-from/package.json +1 -1
  58. package/node_modules/@herbertgao/sol-pi/README.md +3 -3
  59. package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +4 -4
  60. package/node_modules/@herbertgao/sol-pi/agents-install.md +4 -4
  61. package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +6 -6
  62. package/node_modules/@herbertgao/sol-pi/package.json +2 -2
  63. package/node_modules/@narumitw/pi-btw/dist/index.ts +39 -89
  64. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
  65. package/node_modules/@narumitw/pi-btw/package.json +4 -4
  66. package/node_modules/@narumitw/pi-btw/src/btw.ts +28 -87
  67. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +8 -0
  68. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +40 -37
  69. package/node_modules/@narumitw/pi-caffeinate/README.md +21 -66
  70. package/node_modules/@narumitw/pi-caffeinate/dist/index.ts +10 -41
  71. package/node_modules/@narumitw/pi-caffeinate/dist/index.ts.map +2 -2
  72. package/node_modules/@narumitw/pi-caffeinate/package.json +50 -51
  73. package/node_modules/@narumitw/pi-caffeinate/src/caffeinate.ts +637 -663
  74. package/node_modules/@narumitw/pi-caffeinate/src/dbus-inhibit.ts +114 -120
  75. package/node_modules/@narumitw/pi-caffeinate/src/inhibitor-process.ts +29 -29
  76. package/node_modules/@narumitw/pi-caffeinate/src/inhibitors.ts +108 -126
  77. package/node_modules/@narumitw/pi-caffeinate/src/settings.ts +124 -128
  78. package/node_modules/pi-multi-account/CHANGELOG.md +1209 -0
  79. package/node_modules/pi-multi-account/CONTRIBUTING.md +61 -0
  80. package/node_modules/pi-multi-account/LICENSE +21 -0
  81. package/node_modules/pi-multi-account/README.md +197 -0
  82. package/node_modules/pi-multi-account/SECURITY.md +27 -0
  83. package/node_modules/pi-multi-account/auth-file-transaction.ts +56 -0
  84. package/node_modules/pi-multi-account/child-usability.ts +233 -0
  85. package/node_modules/pi-multi-account/compaction-summary.ts +32 -0
  86. package/node_modules/pi-multi-account/completion-route-planner.ts +224 -0
  87. package/node_modules/pi-multi-account/context-guard.ts +420 -0
  88. package/node_modules/pi-multi-account/cursor/LICENSE +21 -0
  89. package/node_modules/pi-multi-account/cursor/NOTICE +2 -0
  90. package/node_modules/pi-multi-account/cursor/auth.ts +165 -0
  91. package/node_modules/pi-multi-account/cursor/bridge-handle.ts +155 -0
  92. package/node_modules/pi-multi-account/cursor/conversation-registry.ts +104 -0
  93. package/node_modules/pi-multi-account/cursor/cursor-models-raw.json +611 -0
  94. package/node_modules/pi-multi-account/cursor/cursor-shared.ts +192 -0
  95. package/node_modules/pi-multi-account/cursor/h2-bridge.mjs +175 -0
  96. package/node_modules/pi-multi-account/cursor/index.ts +572 -0
  97. package/node_modules/pi-multi-account/cursor/message-parsing.ts +323 -0
  98. package/node_modules/pi-multi-account/cursor/prompt-usage.ts +53 -0
  99. package/node_modules/pi-multi-account/cursor/proto/agent_pb.ts +15294 -0
  100. package/node_modules/pi-multi-account/cursor/proxy.ts +2510 -0
  101. package/node_modules/pi-multi-account/cursor/session-lifecycle.ts +40 -0
  102. package/node_modules/pi-multi-account/cursor/sse-keepalive.ts +24 -0
  103. package/node_modules/pi-multi-account/cursor/stream-lifecycle.ts +193 -0
  104. package/node_modules/pi-multi-account/cursor/upstream-watchdog.ts +88 -0
  105. package/node_modules/pi-multi-account/cursor-bridge.ts +240 -0
  106. package/node_modules/pi-multi-account/cursor-model-name.ts +12 -0
  107. package/node_modules/pi-multi-account/index.ts +11825 -0
  108. package/node_modules/pi-multi-account/model-catalog.ts +354 -0
  109. package/node_modules/pi-multi-account/package.json +101 -0
  110. package/node_modules/pi-multi-account/pi-contract.ts +281 -0
  111. package/node_modules/pi-multi-account/provider-payload-stream.ts +44 -0
  112. package/node_modules/pi-multi-account/provider-priority.ts +189 -0
  113. package/node_modules/pi-multi-account/slot-proxy-auth.ts +167 -0
  114. package/node_modules/pi-multi-account/slot-proxy.ts +344 -0
  115. package/node_modules/pi-multi-account/state-file-transaction.ts +67 -0
  116. package/node_modules/pi-multi-account/usage.ts +1099 -0
  117. package/node_modules/pi-typesafe/README.md +6 -2
  118. package/node_modules/pi-typesafe/dist/client.d.ts +11 -0
  119. package/node_modules/pi-typesafe/dist/client.js +45 -10
  120. package/node_modules/pi-typesafe/dist/index.d.ts +2 -2
  121. package/node_modules/pi-typesafe/dist/index.js +1 -1
  122. package/node_modules/pi-typesafe/package.json +2 -2
  123. package/node_modules/pi-web-access/CHANGELOG.md +36 -0
  124. package/node_modules/pi-web-access/README.md +75 -18
  125. package/node_modules/pi-web-access/anysearch.ts +4 -15
  126. package/node_modules/pi-web-access/bocha.ts +3 -22
  127. package/node_modules/pi-web-access/brave.ts +3 -21
  128. package/node_modules/pi-web-access/brightdata.ts +5 -32
  129. package/node_modules/pi-web-access/content-find.ts +168 -53
  130. package/node_modules/pi-web-access/curator-page.ts +4 -1
  131. package/node_modules/pi-web-access/curator-run.ts +2 -1
  132. package/node_modules/pi-web-access/curator-server.ts +1 -0
  133. package/node_modules/pi-web-access/dist/index.js +24620 -0
  134. package/node_modules/pi-web-access/domain-filter-normalization.ts +14 -0
  135. package/node_modules/pi-web-access/duckduckgo.ts +3 -21
  136. package/node_modules/pi-web-access/extract.ts +3 -1
  137. package/node_modules/pi-web-access/firecrawl.ts +5 -29
  138. package/node_modules/pi-web-access/gemini-search.ts +81 -32
  139. package/node_modules/pi-web-access/index.ts +149 -148
  140. package/node_modules/pi-web-access/jina-search.ts +4 -15
  141. package/node_modules/pi-web-access/kagi.ts +4 -13
  142. package/node_modules/pi-web-access/kimi-search.ts +5 -30
  143. package/node_modules/pi-web-access/mistral-search.ts +1 -15
  144. package/node_modules/pi-web-access/ollama.ts +2 -7
  145. package/node_modules/pi-web-access/openai-search.ts +174 -36
  146. package/node_modules/pi-web-access/opencode-session-headers.ts +24 -0
  147. package/node_modules/pi-web-access/package.json +10 -4
  148. package/node_modules/pi-web-access/page-query.ts +10 -2
  149. package/node_modules/pi-web-access/parallel.ts +1 -15
  150. package/node_modules/pi-web-access/pdf-extract.ts +3 -0
  151. package/node_modules/pi-web-access/querit.ts +5 -29
  152. package/node_modules/pi-web-access/search-answer-formatting.ts +11 -0
  153. package/node_modules/pi-web-access/search-result-count-normalization.ts +4 -0
  154. package/node_modules/pi-web-access/search1api.ts +5 -29
  155. package/node_modules/pi-web-access/searchinfinity.ts +5 -29
  156. package/node_modules/pi-web-access/searxng.ts +3 -21
  157. package/node_modules/pi-web-access/serpapi.ts +5 -28
  158. package/node_modules/pi-web-access/serpbase.ts +3 -22
  159. package/node_modules/pi-web-access/serpdive.ts +3 -21
  160. package/node_modules/pi-web-access/serper.ts +5 -28
  161. package/node_modules/pi-web-access/serply.ts +197 -0
  162. package/node_modules/pi-web-access/source-check.ts +11 -47
  163. package/node_modules/pi-web-access/summary-review.ts +7 -3
  164. package/node_modules/pi-web-access/tavily.ts +3 -21
  165. package/node_modules/pi-web-access/tinyfish.ts +5 -29
  166. package/node_modules/pi-web-access/utils.ts +9 -1
  167. package/node_modules/pi-web-access/valyu.ts +5 -28
  168. package/node_modules/pi-web-access/xai-search.ts +1 -15
  169. package/node_modules/pi-web-access/xcrawl.ts +5 -32
  170. package/package.json +17 -11
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Multi-account surface for the Cursor provider.
3
+ *
4
+ * The single-account extension (`index.ts`) keeps ONE module-level access token and
5
+ * registers ONE provider called `cursor`. Rotation needs N providers — `cursor`,
6
+ * `cursor-account-2`, ... — that share one local proxy while each authenticates as its
7
+ * own Cursor subscription.
8
+ *
9
+ * The account is carried by the request itself: every slot's `getApiKey` returns THAT
10
+ * slot's access token, Pi puts it in the `Authorization` header of the call to the local
11
+ * proxy, and the proxy hands the very same token to Cursor. Nothing is cached between
12
+ * requests, so a failover mid-session takes effect on the next call instead of silently
13
+ * re-using the spent account's token.
14
+ *
15
+ * Consumed by pi-multi-account's `cursor-bridge.ts`.
16
+ */
17
+
18
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
19
+ import { cursorPayloadStream } from "../provider-payload-stream.ts";
20
+ import {
21
+ generateCursorAuthParams,
22
+ getTokenExpiry,
23
+ pollCursorAuth,
24
+ refreshCursorToken,
25
+ } from "./auth.ts";
26
+ import { FALLBACK_MODELS, modelConfig, processModels } from "./index.ts";
27
+ import { getCursorModels, startProxy, type CursorModel } from "./proxy.ts";
28
+
29
+ export const CURSOR_BASE = "cursor";
30
+
31
+ /** Resolves the stored access token for a provider id (reads the host's auth.json). */
32
+ type TokenResolver = (providerId: string) => Promise<string>;
33
+
34
+ type AuthEntryLike = {
35
+ type?: string;
36
+ access?: string;
37
+ refresh?: string;
38
+ expires?: number;
39
+ };
40
+
41
+ type RegisterOptions = {
42
+ /**
43
+ * Host guard that refuses a login when the SAME real Cursor account is already stored
44
+ * in another slot. Throwing here is correct: two slots on one account is not a rotation,
45
+ * it is one account that would be benched twice.
46
+ */
47
+ rejectDuplicateLogin?: (slot: string, credentials: AuthEntryLike) => AuthEntryLike;
48
+ /** Called with the real catalog once a token can read it, so the host can re-register. */
49
+ onModelsDiscovered?: (models: CursorModel[]) => void;
50
+ };
51
+
52
+ // `var`, deliberately: these are hoisted, so even a caller that reaches this module while it
53
+ // is still initializing (a concurrent import) sees `undefined` instead of a temporal-dead-zone
54
+ // crash. The module is shared by every account slot and must never be the reason a session
55
+ // loses Cursor.
56
+ // eslint-disable-next-line no-var
57
+ var proxyPromise: Promise<number> | undefined;
58
+ // eslint-disable-next-line no-var
59
+ var tokenResolver: TokenResolver | undefined;
60
+
61
+ /**
62
+ * The bearer token Pi attached to this proxy call, i.e. the identity of the slot that
63
+ * made it. `cursor-proxy` is the single-account extension's placeholder and carries no
64
+ * identity, so it is treated as absent.
65
+ */
66
+ function bearerFromRequest(req?: {
67
+ headers?: Record<string, string | string[] | undefined>;
68
+ }): string | undefined {
69
+ const raw = req?.headers?.authorization;
70
+ const header = Array.isArray(raw) ? raw[0] : raw;
71
+ if (typeof header !== "string") return undefined;
72
+ const match = /^Bearer\s+(.+)$/i.exec(header.trim());
73
+ const token = match?.[1]?.trim();
74
+ if (!token || token === "cursor-proxy") return undefined;
75
+ return token;
76
+ }
77
+
78
+ /**
79
+ * Start (once) the shared local proxy. Idempotent: later calls only refresh the fallback
80
+ * resolver and return the port already bound, so every slot registers against one port.
81
+ */
82
+ export async function ensureCursorProxy(resolve: TokenResolver): Promise<number> {
83
+ tokenResolver = resolve;
84
+ proxyPromise ??= startProxy(async (req) => {
85
+ const fromRequest = bearerFromRequest(req);
86
+ if (fromRequest) return fromRequest;
87
+ // No usable token on the request (a legacy placeholder, or a caller that sends none):
88
+ // fall back to the base slot's stored credential rather than failing the call.
89
+ const fallback = (await tokenResolver?.(CURSOR_BASE)) ?? "";
90
+ if (!fallback) {
91
+ throw new Error(
92
+ "No Cursor account is logged in for this request. Run /login and pick a cursor slot.",
93
+ );
94
+ }
95
+ return fallback;
96
+ });
97
+ return proxyPromise;
98
+ }
99
+
100
+ function displayName(id: string): string {
101
+ return id === CURSOR_BASE ? "Cursor" : `Cursor (${id})`;
102
+ }
103
+
104
+ async function discoverModels(
105
+ accessToken: string,
106
+ onModelsDiscovered?: (models: CursorModel[]) => void,
107
+ ): Promise<void> {
108
+ if (!onModelsDiscovered) return;
109
+ try {
110
+ const discovered = await getCursorModels(accessToken);
111
+ if (discovered.length > 0) onModelsDiscovered(discovered);
112
+ } catch {
113
+ // Catalog discovery is an enhancement over FALLBACK_MODELS. A login must never fail
114
+ // because Cursor's model list could not be read.
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Register one Cursor account slot as its own Pi provider.
120
+ *
121
+ * Safe to call repeatedly for the same id — that is how a freshly discovered catalog
122
+ * replaces the fallback list.
123
+ */
124
+ export function registerCursorProvider(
125
+ pi: ExtensionAPI,
126
+ id: string,
127
+ proxyPort: number,
128
+ rawModels: CursorModel[] = FALLBACK_MODELS,
129
+ options: RegisterOptions = {},
130
+ ): void {
131
+ const name = displayName(id);
132
+ const models = processModels(rawModels).map(modelConfig);
133
+
134
+ (pi as any).registerProvider(id, {
135
+ name,
136
+ baseUrl: `http://127.0.0.1:${proxyPort}/v1`,
137
+ api: "openai-completions",
138
+ streamSimple: cursorPayloadStream,
139
+ models,
140
+ oauth: {
141
+ name,
142
+ isSubscription: true,
143
+
144
+ async login(callbacks: any): Promise<AuthEntryLike> {
145
+ const { verifier, uuid, loginUrl } = await generateCursorAuthParams();
146
+ callbacks.onAuth({ url: loginUrl });
147
+ const { accessToken, refreshToken } = await pollCursorAuth(uuid, verifier);
148
+ const credentials: AuthEntryLike = {
149
+ type: "oauth",
150
+ access: accessToken,
151
+ refresh: refreshToken,
152
+ expires: getTokenExpiry(accessToken),
153
+ };
154
+ // Reject BEFORE the catalog call: a duplicate must cost nothing and change nothing.
155
+ const verified = options.rejectDuplicateLogin
156
+ ? options.rejectDuplicateLogin(id, credentials)
157
+ : credentials;
158
+ await discoverModels(accessToken, options.onModelsDiscovered);
159
+ return verified;
160
+ },
161
+
162
+ async refreshToken(credentials: AuthEntryLike): Promise<AuthEntryLike> {
163
+ const refreshed = await refreshCursorToken(credentials.refresh as string);
164
+ await discoverModels(refreshed.access, options.onModelsDiscovered);
165
+ return { type: "oauth", ...refreshed };
166
+ },
167
+
168
+ /**
169
+ * THIS slot's token, not a shared placeholder — it is what tells the proxy which
170
+ * Cursor account the request belongs to.
171
+ */
172
+ getApiKey(credentials: AuthEntryLike): string {
173
+ return credentials.access ?? "";
174
+ },
175
+ },
176
+ });
177
+ }
178
+
179
+ export { FALLBACK_MODELS };
180
+
181
+ /**
182
+ * Read the account's real model catalog.
183
+ *
184
+ * THROWS the underlying error: callers (login, refresh, startup discovery) each decide
185
+ * whether a failed catalog read is fatal — swallowing it here left every layer above
186
+ * guessing why the fallback list was still in effect.
187
+ */
188
+ export async function discoverCursorModels(
189
+ accessToken: string,
190
+ ): Promise<CursorModel[]> {
191
+ return getCursorModels(accessToken);
192
+ }
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Dumb HTTP/2 bidirectional pipe for Cursor gRPC.
4
+ *
5
+ * Originally from https://github.com/ephraimduncan/opencode-cursor by Ephraim Duncan (MIT).
6
+ *
7
+ * Bun's node:http2 is broken. This Node script acts as a transparent
8
+ * HTTP/2 proxy: it opens a single bidirectional stream and ferries
9
+ * raw bytes between the parent process (via stdin/stdout) and Cursor.
10
+ *
11
+ * Protocol (length-prefixed framing over stdin/stdout):
12
+ * [4 bytes big-endian length][payload]
13
+ *
14
+ * First message on stdin is JSON config:
15
+ * { "accessToken": "...", "url": "...", "path": "...", "unary": false }
16
+ *
17
+ * When unary=true, the bridge uses application/proto (raw protobuf) instead
18
+ * of application/connect+proto (Connect streaming). The single stdin message
19
+ * is written as the request body and the stream is ended immediately.
20
+ * After config, subsequent stdin messages are raw bytes to write to the H2 stream.
21
+ * H2 response data is written to stdout using the same length-prefixed framing.
22
+ */
23
+ import http2 from "node:http2";
24
+ import crypto from "node:crypto";
25
+
26
+ const CURSOR_CLIENT_VERSION = "cli-2026.01.09-231024f";
27
+
28
+ /** Write one length-prefixed message to stdout. */
29
+ function writeMessage(data) {
30
+ const lenBuf = Buffer.alloc(4);
31
+ lenBuf.writeUInt32BE(data.length, 0);
32
+ process.stdout.write(lenBuf);
33
+ process.stdout.write(data);
34
+ }
35
+
36
+ // --- Buffered stdin reader ---
37
+
38
+ let stdinBuf = Buffer.alloc(0);
39
+ let stdinResolve = null;
40
+ let stdinEnded = false;
41
+
42
+ process.stdin.on("data", (chunk) => {
43
+ stdinBuf = Buffer.concat([stdinBuf, chunk]);
44
+ if (stdinResolve) {
45
+ const r = stdinResolve;
46
+ stdinResolve = null;
47
+ r();
48
+ }
49
+ });
50
+
51
+ process.stdin.on("end", () => {
52
+ stdinEnded = true;
53
+ if (stdinResolve) {
54
+ const r = stdinResolve;
55
+ stdinResolve = null;
56
+ r();
57
+ }
58
+ });
59
+
60
+ function waitForData() {
61
+ return new Promise((resolve) => { stdinResolve = resolve; });
62
+ }
63
+
64
+ async function readExact(n) {
65
+ while (stdinBuf.length < n) {
66
+ if (stdinEnded) return null;
67
+ await waitForData();
68
+ }
69
+ const result = stdinBuf.subarray(0, n);
70
+ stdinBuf = stdinBuf.subarray(n);
71
+ return Buffer.from(result);
72
+ }
73
+
74
+ async function readMessage() {
75
+ const lenBuf = await readExact(4);
76
+ if (!lenBuf) return null;
77
+ const len = lenBuf.readUInt32BE(0);
78
+ if (len === 0) return Buffer.alloc(0);
79
+ return readExact(len);
80
+ }
81
+
82
+ // --- Main ---
83
+
84
+ const configBuf = await readMessage();
85
+ if (!configBuf) process.exit(1);
86
+
87
+ const config = JSON.parse(configBuf.toString("utf8"));
88
+ const { accessToken, url, path: rpcPath, unary } = config;
89
+
90
+ const client = http2.connect(url || "https://api2.cursor.sh");
91
+
92
+ // Guard against initial connection failure. Reset on any h2 activity
93
+ // so long-running agent conversations (with tool call round-trips) survive.
94
+ let timeout = setTimeout(killBridge, 30_000);
95
+
96
+ function resetTimeout() {
97
+ clearTimeout(timeout);
98
+ timeout = setTimeout(killBridge, 120_000);
99
+ }
100
+
101
+ function killBridge() {
102
+ clearTimeout(timeout);
103
+ client.destroy();
104
+ process.exit(1);
105
+ }
106
+
107
+ client.on("error", () => {
108
+ clearTimeout(timeout);
109
+ process.exit(1);
110
+ });
111
+
112
+ const headers = {
113
+ ":method": "POST",
114
+ ":path": rpcPath || "/agent.v1.AgentService/Run",
115
+ "content-type": unary ? "application/proto" : "application/connect+proto",
116
+ te: "trailers",
117
+ authorization: `Bearer ${accessToken}`,
118
+ "x-ghost-mode": "true",
119
+ "x-cursor-client-version": CURSOR_CLIENT_VERSION,
120
+ "x-cursor-client-type": "cli",
121
+ "x-request-id": crypto.randomUUID(),
122
+ };
123
+ if (!unary) {
124
+ headers["connect-protocol-version"] = "1";
125
+ }
126
+ const h2Stream = client.request(headers);
127
+
128
+ // Forward H2 response data → stdout (length-prefixed)
129
+ h2Stream.on("data", (chunk) => {
130
+ resetTimeout();
131
+ writeMessage(chunk);
132
+ });
133
+
134
+ h2Stream.on("end", () => {
135
+ clearTimeout(timeout);
136
+ client.close();
137
+ // Give stdout time to flush
138
+ setTimeout(() => process.exit(0), 100);
139
+ });
140
+
141
+ h2Stream.on("error", () => {
142
+ clearTimeout(timeout);
143
+ client.close();
144
+ process.exit(1);
145
+ });
146
+
147
+ // Forward stdin → H2 stream (after config message)
148
+ if (unary) {
149
+ // Unary mode: read a single body message, write it, and end the stream.
150
+ const body = await readMessage();
151
+ if (body && body.length > 0 && !h2Stream.closed && !h2Stream.destroyed) {
152
+ h2Stream.end(body);
153
+ } else {
154
+ h2Stream.end();
155
+ }
156
+ } else {
157
+ // Streaming mode: forward all stdin messages as Connect frames.
158
+ (async () => {
159
+ while (true) {
160
+ const msg = await readMessage();
161
+ if (!msg || msg.length === 0) {
162
+ // EOF or zero-length = done writing
163
+ break;
164
+ }
165
+ if (!h2Stream.closed && !h2Stream.destroyed) {
166
+ resetTimeout();
167
+ h2Stream.write(msg);
168
+ }
169
+ }
170
+
171
+ if (!h2Stream.closed && !h2Stream.destroyed) {
172
+ h2Stream.end();
173
+ }
174
+ })();
175
+ }