Skip to content

Commit c261c95

Browse files
fix(cursor-plugin): open /activate for Device Code + slower poll on 429
1 parent 8f93e54 commit c261c95

2 files changed

Lines changed: 24 additions & 5 deletions

File tree

plugins/agentstack/hooks/scripts/device-code.mjs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { join } from 'node:path';
77
import { homedir, platform } from 'node:os';
88
import { exec } from 'node:child_process';
99
import { randomUUID } from 'node:crypto';
10-
import { pollDeviceToken, loadConfidentialClient } from '../../lib/plugin-kernel/deviceCodeClient.mjs';
10+
import { pollDeviceToken, loadConfidentialClient, deviceCodeActivateUrl } from '../../lib/plugin-kernel/deviceCodeClient.mjs';
1111
import { tenantActionsFromCatalog } from '../../lib/plugin-kernel/mcpActionsCatalog.mjs';
1212
import { applyAgentstackMcpBearer } from '../../lib/plugin-kernel/mcpConfig.mjs';
1313

@@ -165,11 +165,12 @@ async function main() {
165165
console.log(' Trace: ' + traceId);
166166
const init = await authorize(scopes, traceId);
167167

168-
console.log('\n Open: ' + (init.verification_uri_complete || init.verification_uri));
168+
const activateLink = deviceCodeActivateUrl(BASE_URL, init.user_code);
169+
console.log('\n Open: ' + activateLink);
169170
console.log(' Code: ' + init.user_code + '\n');
170171
console.log(' (Waiting for approval — this will return automatically.)\n');
171172

172-
if (!headless) await openBrowser(init.verification_uri_complete || init.verification_uri);
173+
if (!headless) await openBrowser(activateLink);
173174

174175
const token = await pollDeviceToken({
175176
tokenUrl: `${BASE_URL}/api/oauth2/token`,

plugins/agentstack/lib/plugin-kernel/deviceCodeClient.mjs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ export async function loadConfidentialClient({
4949
};
5050
}
5151

52+
/** Human-facing approve URL (SPA /activate — not POST /api/oauth2/device/verify). */
53+
export function deviceCodeActivateUrl(baseUrl, userCode) {
54+
const code = String(userCode || '').trim();
55+
const base = String(baseUrl || 'https://agentstack.tech').replace(/\/$/, '');
56+
return `${base}/activate?user_code=${encodeURIComponent(code)}`;
57+
}
58+
5259
/**
5360
* POST application/x-www-form-urlencoded.
5461
* OAuth token endpoints often return HTTP 400 with `{ error: "authorization_pending" }` —
@@ -83,7 +90,8 @@ export async function pollDeviceToken({
8390
clientSecret = null,
8491
}) {
8592
const deadline = Date.now() + expiresInSec * 1000;
86-
let waitMs = Math.max(1000, intervalSec * 1000);
93+
// Prod oauth2_token RL is 5 req/min per IP — stay under that even when server interval=5.
94+
let waitMs = Math.max(12000, intervalSec * 1000);
8795

8896
while (Date.now() < deadline) {
8997
await new Promise((r) => setTimeout(r, waitMs));
@@ -93,7 +101,17 @@ export async function pollDeviceToken({
93101
client_id: clientId,
94102
};
95103
if (clientSecret) params.client_secret = clientSecret;
96-
const token = await postForm(tokenUrl, params, traceId);
104+
let token;
105+
try {
106+
token = await postForm(tokenUrl, params, traceId);
107+
} catch (err) {
108+
const msg = String(err?.message || err);
109+
if (msg.includes('HTTP 429')) {
110+
waitMs = Math.min(waitMs + 5000, 30000);
111+
continue;
112+
}
113+
throw err;
114+
}
97115
if (token.access_token) return token;
98116
if (token.error === 'authorization_pending') continue;
99117
if (token.error === 'slow_down') {

0 commit comments

Comments
 (0)