From 9b1e13fab2ad2cbc330828a4d8ec89b49cba0888 Mon Sep 17 00:00:00 2001 From: Mert Can Demir Date: Thu, 4 Jun 2026 12:37:07 +0300 Subject: [PATCH 1/3] feat: add support for reading authentication credentials from Devin - Next app --- plugins/devin/plugin.js | 25 +++++++++++++++++++++---- plugins/devin/plugin.test.js | 27 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/plugins/devin/plugin.js b/plugins/devin/plugin.js index 9cbadeb2..92b44e6f 100644 --- a/plugins/devin/plugin.js +++ b/plugins/devin/plugin.js @@ -3,7 +3,16 @@ var DEFAULT_API_SERVER_URL = "https://server.codeium.com" var CLOUD_COMPAT_VERSION = "1.108.2" var CREDENTIALS_PATH = "~/.local/share/devin/credentials.toml" - var STATE_DB = "~/Library/Application Support/Devin/User/globalStorage/state.vscdb" + var APP_AUTH_SOURCES = [ + { + source: "Devin app", + stateDb: "~/Library/Application Support/Devin/User/globalStorage/state.vscdb", + }, + { + source: "Devin - Next app", + stateDb: "~/Library/Application Support/Devin - Next/User/globalStorage/state.vscdb", + }, + ] var LOGIN_HINT = "Run devin auth login or sign in to Devin and try again." var QUOTA_HINT = "Devin quota data unavailable. Try again later." var DAY_MS = 24 * 60 * 60 * 1000 @@ -129,9 +138,17 @@ } function loadAppAuth(ctx) { + for (var i = 0; i < APP_AUTH_SOURCES.length; i++) { + var auth = readAppAuth(ctx, APP_AUTH_SOURCES[i]) + if (auth) return auth + } + return null + } + + function readAppAuth(ctx, variant) { try { var rows = ctx.host.sqlite.query( - STATE_DB, + variant.stateDb, "SELECT value FROM ItemTable WHERE key = 'windsurfAuthStatus' LIMIT 1" ) var parsed = ctx.util.tryParseJson(rows) @@ -141,10 +158,10 @@ return { apiKey: auth.apiKey, apiServerUrl: null, - source: "Devin app", + source: variant.source, } } catch (e) { - ctx.host.log.warn("failed to read Devin app auth: " + String(e)) + ctx.host.log.warn("failed to read " + variant.source + " auth: " + String(e)) return null } } diff --git a/plugins/devin/plugin.test.js b/plugins/devin/plugin.test.js index b3058a2b..07cad864 100644 --- a/plugins/devin/plugin.test.js +++ b/plugins/devin/plugin.test.js @@ -3,6 +3,7 @@ import { makeCtx } from "../test-helpers.js" const CREDENTIALS_PATH = "~/.local/share/devin/credentials.toml" const STATE_DB = "~/Library/Application Support/Devin/User/globalStorage/state.vscdb" +const NEXT_STATE_DB = "~/Library/Application Support/Devin - Next/User/globalStorage/state.vscdb" const DEFAULT_API_SERVER_URL = "https://server.codeium.com" const CLOUD_COMPAT_VERSION = "1.108.2" @@ -150,6 +151,32 @@ describe("devin plugin", () => { expect(sentBody.metadata.apiKey).toBe("devin-session-token$app") }) + it("reads auth from the Devin - Next app when stable Devin is absent", async () => { + const ctx = makeCtx() + ctx.host.sqlite.query.mockImplementation((db, sql) => { + expect(String(sql)).toContain("windsurfAuthStatus") + if (db === NEXT_STATE_DB) return makeAuthStatus("devin-session-token$next") + return "[]" + }) + ctx.host.http.request.mockReturnValue({ + status: 200, + bodyText: JSON.stringify(makeQuotaResponse({ planInfo: { planName: "Pro" } })), + }) + + const plugin = await loadPlugin() + const result = plugin.probe(ctx) + + expect(result.plan).toBe("Pro") + const queriedDbs = ctx.host.sqlite.query.mock.calls.map(([db]) => db) + expect(queriedDbs).toContain(STATE_DB) + expect(queriedDbs).toContain(NEXT_STATE_DB) + const sentBody = JSON.parse(String(ctx.host.http.request.mock.calls[0][0].bodyText)) + expect(sentBody.metadata.apiKey).toBe("devin-session-token$next") + expect(ctx.host.log.info).toHaveBeenCalledWith( + expect.stringContaining("Devin quota diagnostics source=Devin - Next app") + ) + }) + it("ignores plaintext API server URLs from CLI credentials", async () => { const ctx = makeCtx() ctx.host.fs.writeText(CREDENTIALS_PATH, makeCredentialsToml({ From 2a6b5809d97ec0a620d5972787f84d31e761959a Mon Sep 17 00:00:00 2001 From: Mert Can Demir Date: Thu, 4 Jun 2026 12:51:23 +0300 Subject: [PATCH 2/3] fix: try every Devin install's token instead of only the first probe stopped at the first app install that had a token, so a stale token in one install (e.g. stable Devin) masked a valid one in another (Devin - Next) and surfaced "Run devin auth login". Collect tokens from all installs and try each one the cloud hasn't already rejected, deduping by (apiKey, server) so a token is never replayed across the CLI and app sources. --- plugins/devin/plugin.js | 38 +++++++++++++++++++++++++----------- plugins/devin/plugin.test.js | 33 +++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/plugins/devin/plugin.js b/plugins/devin/plugin.js index 92b44e6f..2fb33ed1 100644 --- a/plugins/devin/plugin.js +++ b/plugins/devin/plugin.js @@ -137,12 +137,13 @@ } } - function loadAppAuth(ctx) { + function loadAppAuths(ctx) { + var auths = [] for (var i = 0; i < APP_AUTH_SOURCES.length; i++) { var auth = readAppAuth(ctx, APP_AUTH_SOURCES[i]) - if (auth) return auth + if (auth) auths.push(auth) } - return null + return auths } function readAppAuth(ctx, variant) { @@ -295,23 +296,26 @@ function probe(ctx) { var sawApiKey = false var sawAuthFailure = false - var credentials = loadCredentialsFile(ctx) + var attempts = [] + var credentials = loadCredentialsFile(ctx) if (credentials) { sawApiKey = true + attempts.push(authFingerprint(credentials)) var credentialsAttempt = tryAuth(ctx, credentials) if (credentialsAttempt.output) return credentialsAttempt.output if (credentialsAttempt.authFailure) sawAuthFailure = true } - var appAuth = loadAppAuth(ctx) - if ( - appAuth && - (!credentials || - appAuth.apiKey !== credentials.apiKey || - effectiveApiServerUrl(appAuth) !== effectiveApiServerUrl(credentials)) - ) { + // Walk every app install (stable, then "- Next") and try each token the cloud + // hasn't already rejected, so a stale token in one install doesn't mask a + // valid one in another. + var appAuths = loadAppAuths(ctx) + for (var i = 0; i < appAuths.length; i++) { + var appAuth = appAuths[i] + if (alreadyAttempted(attempts, appAuth)) continue sawApiKey = true + attempts.push(authFingerprint(appAuth)) var appAttempt = tryAuth(ctx, appAuth) if (appAttempt.output) return appAttempt.output if (appAttempt.authFailure) sawAuthFailure = true @@ -322,5 +326,17 @@ throw LOGIN_HINT } + function authFingerprint(auth) { + return auth.apiKey + "\n" + effectiveApiServerUrl(auth) + } + + function alreadyAttempted(attempts, auth) { + var fingerprint = authFingerprint(auth) + for (var i = 0; i < attempts.length; i++) { + if (attempts[i] === fingerprint) return true + } + return false + } + globalThis.__openusage_plugin = { id: "devin", probe: probe } })() diff --git a/plugins/devin/plugin.test.js b/plugins/devin/plugin.test.js index 07cad864..26be7a7f 100644 --- a/plugins/devin/plugin.test.js +++ b/plugins/devin/plugin.test.js @@ -58,9 +58,8 @@ function makeQuotaResponse(overrides = {}) { function mockAppAuth(ctx, apiKey = "devin-session-token$app") { ctx.host.sqlite.query.mockImplementation((db, sql) => { - expect(db).toBe(STATE_DB) expect(String(sql)).toContain("windsurfAuthStatus") - return makeAuthStatus(apiKey) + return db === STATE_DB ? makeAuthStatus(apiKey) : "[]" }) } @@ -177,6 +176,36 @@ describe("devin plugin", () => { ) }) + it("falls back from a stale stable-Devin token to the Devin - Next app", async () => { + const ctx = makeCtx() + ctx.host.sqlite.query.mockImplementation((db, sql) => { + expect(String(sql)).toContain("windsurfAuthStatus") + if (db === STATE_DB) return makeAuthStatus("devin-session-token$stable") + if (db === NEXT_STATE_DB) return makeAuthStatus("devin-session-token$next") + return "[]" + }) + ctx.host.http.request.mockImplementation((request) => { + const body = JSON.parse(String(request.bodyText)) + if (body.metadata.apiKey === "devin-session-token$stable") { + return { status: 401, bodyText: "{}" } + } + return { + status: 200, + bodyText: JSON.stringify(makeQuotaResponse({ planInfo: { planName: "Teams" } })), + } + }) + + const plugin = await loadPlugin() + const result = plugin.probe(ctx) + + expect(result.plan).toBe("Teams") + expect(ctx.host.http.request).toHaveBeenCalledTimes(2) + const triedKeys = ctx.host.http.request.mock.calls.map( + ([request]) => JSON.parse(String(request.bodyText)).metadata.apiKey + ) + expect(triedKeys).toEqual(["devin-session-token$stable", "devin-session-token$next"]) + }) + it("ignores plaintext API server URLs from CLI credentials", async () => { const ctx = makeCtx() ctx.host.fs.writeText(CREDENTIALS_PATH, makeCredentialsToml({ From 63871090f46db1dbdaf13f128ce4fe7bcc84ff76 Mon Sep 17 00:00:00 2001 From: Mert Can Demir Date: Thu, 4 Jun 2026 13:12:53 +0300 Subject: [PATCH 3/3] refactor: read each Devin install's auth only until one succeeds --- plugins/devin/plugin.js | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/plugins/devin/plugin.js b/plugins/devin/plugin.js index 2fb33ed1..4f245d5f 100644 --- a/plugins/devin/plugin.js +++ b/plugins/devin/plugin.js @@ -137,15 +137,6 @@ } } - function loadAppAuths(ctx) { - var auths = [] - for (var i = 0; i < APP_AUTH_SOURCES.length; i++) { - var auth = readAppAuth(ctx, APP_AUTH_SOURCES[i]) - if (auth) auths.push(auth) - } - return auths - } - function readAppAuth(ctx, variant) { try { var rows = ctx.host.sqlite.query( @@ -309,10 +300,11 @@ // Walk every app install (stable, then "- Next") and try each token the cloud // hasn't already rejected, so a stale token in one install doesn't mask a - // valid one in another. - var appAuths = loadAppAuths(ctx) - for (var i = 0; i < appAuths.length; i++) { - var appAuth = appAuths[i] + // valid one in another. Read each state DB only when we reach it, so a working + // earlier source short-circuits before we touch a later install's DB. + for (var i = 0; i < APP_AUTH_SOURCES.length; i++) { + var appAuth = readAppAuth(ctx, APP_AUTH_SOURCES[i]) + if (!appAuth) continue if (alreadyAttempted(attempts, appAuth)) continue sawApiKey = true attempts.push(authFingerprint(appAuth))