@magpie-community/opencode-workbuddy-auth 0.1.30 → 0.1.31
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/README.md +16 -0
- package/index.mjs +208 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,6 +56,22 @@ Desktop's sign-in is refreshed only when the app hasn't refreshed it
|
|
|
56
56
|
itself, and the new token is kept in memory, never written back, as magpie's
|
|
57
57
|
built-in WorkBuddy does.
|
|
58
58
|
|
|
59
|
+
A chat or usage read WorkBuddy answers 401 (since 0.1.31) is renewed once,
|
|
60
|
+
whatever the token's time, and sent once more. A token another process
|
|
61
|
+
saved since is taken first instead of spending the refresh token again. A
|
|
62
|
+
desktop sign-in is never renewed for it: the app's file is read again, and
|
|
63
|
+
its token is used if the app signed in again. A 401 again, or no new
|
|
64
|
+
token, ends in a 401 that says to sign in again (Providers → WorkBuddy →
|
|
65
|
+
this account's Sign in again in magpie; WorkBuddy desktop for a desktop
|
|
66
|
+
sign-in). The plugin marks the account expired in magpie
|
|
67
|
+
(`X-Magpie-Sign-In: expired`, usage's `signIn`) only on WorkBuddy's word
|
|
68
|
+
that the sign-in is gone: the refresh token refused (401, `invalid_grant`),
|
|
69
|
+
or the access token refused (`WWW-Authenticate: … error="invalid_token"`)
|
|
70
|
+
with no refresh token left to renew it with or just after renewing. A
|
|
71
|
+
failed renewal for another reason, or a bare 401 without `invalid_token`,
|
|
72
|
+
leaves the account as it is; a desktop sign-in is never marked. A 401
|
|
73
|
+
after a renewal takes a mark off (`renewed`).
|
|
74
|
+
|
|
59
75
|
## Requests
|
|
60
76
|
|
|
61
77
|
Chats are chat completions (`@ai-sdk/openai-compatible`) at `<API>/chat/completions`.
|
package/index.mjs
CHANGED
|
@@ -1123,6 +1123,8 @@ function readDesktop(site) {
|
|
|
1123
1123
|
uid: acct.uid,
|
|
1124
1124
|
...(typeof acct.enterpriseId === "string" && acct.enterpriseId.trim() ? { enterpriseId: acct.enterpriseId.trim() } : {}),
|
|
1125
1125
|
name: acct.nickname || acct.phoneNumber || acct.uid,
|
|
1126
|
+
// what the errors below say to do goes by it: sign in to the app again
|
|
1127
|
+
source: "desktop",
|
|
1126
1128
|
}
|
|
1127
1129
|
} catch {
|
|
1128
1130
|
return null
|
|
@@ -1237,23 +1239,143 @@ async function unanswered(site, a, body, err, signal) {
|
|
|
1237
1239
|
const size = byteLength(body)
|
|
1238
1240
|
if (size <= EDGE_READS) throw err
|
|
1239
1241
|
let status = 0
|
|
1242
|
+
let challenge = ""
|
|
1240
1243
|
try {
|
|
1241
1244
|
const res = await meterAsked(site, a, "/billing/meter/get-user-resource-summary", {}, AbortSignal.timeout(20000))
|
|
1242
1245
|
status = res.status
|
|
1246
|
+
challenge = res.headers.get("www-authenticate") ?? ""
|
|
1243
1247
|
await res.body?.cancel()
|
|
1244
1248
|
} catch {
|
|
1245
1249
|
throw err
|
|
1246
1250
|
}
|
|
1247
1251
|
if (status !== 401) throw err
|
|
1248
1252
|
const mb = (size / (1024 * 1024)).toFixed(1)
|
|
1253
|
+
const note = `It closes the connection on a refused request over 2 MiB (this one was ${mb} MiB) instead of answering, which is why it read as ECONNRESET.`
|
|
1249
1254
|
const message =
|
|
1250
|
-
`${site.name} refused this account's sign-in (401).
|
|
1251
|
-
`(this one was ${mb} MiB) instead of answering, which is why it read as ECONNRESET. ` +
|
|
1255
|
+
`${site.name} refused this account's sign-in (401). ${note} ` +
|
|
1252
1256
|
(a.source === "desktop" ? `Sign in to ${site.name} desktop again.` : `Sign in to ${site.name} again in magpie.`)
|
|
1257
|
+
// the billing API's challenge goes with it: it says whether the token
|
|
1258
|
+
// itself was refused (invalidToken), as the chat's own 401 would have
|
|
1259
|
+
const headers = { "content-type": "application/json", ...(challenge ? { "www-authenticate": challenge } : {}) }
|
|
1260
|
+
const res = new Response(JSON.stringify({ error: { message, type: "authentication_error", code: 401 } }), {
|
|
1261
|
+
status: 401,
|
|
1262
|
+
statusText: "Unauthorized",
|
|
1263
|
+
headers,
|
|
1264
|
+
})
|
|
1265
|
+
edgeNotes.set(res, note)
|
|
1266
|
+
return res
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// edgeNotes is what unanswered's 401s say of the reset, for signedOut to
|
|
1270
|
+
// say again when the sign-in can't be renewed.
|
|
1271
|
+
const edgeNotes = new WeakMap()
|
|
1272
|
+
|
|
1273
|
+
// ---- a refused sign-in ----------------------------------------------------------
|
|
1274
|
+
|
|
1275
|
+
// invalidToken says res is WorkBuddy refusing the access token itself. Its
|
|
1276
|
+
// front door (APISIX, on both sites) answers a token it doesn't take with
|
|
1277
|
+
// 401 and WWW-Authenticate: Bearer realm="copilot", error="invalid_token",
|
|
1278
|
+
// on a chat (streamed or not) and on the billing API alike; measured on
|
|
1279
|
+
// www.workbuddy.ai on 2026-10-23 with a real account's token cut short,
|
|
1280
|
+
// and with "garbage". (Its /v3/config answers 200 to any token, so the
|
|
1281
|
+
// models list never meets a 401.) A 401 without it is not taken as the
|
|
1282
|
+
// sign-in's: WorkBuddy answers some paths with a bare 401 to an accepted
|
|
1283
|
+
// sign-in (magpie's check-in, モモコ on Discord).
|
|
1284
|
+
const invalidToken = (res) => res?.status === 401 && /invalid_token/i.test(res.headers.get("www-authenticate") ?? "")
|
|
1285
|
+
|
|
1286
|
+
// refreshRefused says e is WorkBuddy turning the refresh token itself
|
|
1287
|
+
// away: HTTP 401 with code 12153, "refresh token failed:400 Bad Request:
|
|
1288
|
+
// invalid_grant: Invalid refresh token" for a spent or unknown one, or
|
|
1289
|
+
// "10000:token format error" (measured 2026-10-23). Anything else (a 5xx,
|
|
1290
|
+
// a timeout) says nothing of the sign-in.
|
|
1291
|
+
const refreshRefused = (e) => e instanceof WBError && (e.status === 401 || /invalid_grant/i.test(e.message))
|
|
1292
|
+
|
|
1293
|
+
// reissued is the sign-in to send a request WorkBuddy refused a's access
|
|
1294
|
+
// token for (401) once more with, before that token's time: WorkBuddy can
|
|
1295
|
+
// refuse one long before it ends (a browser sign-in's lasts as long as
|
|
1296
|
+
// its refresh token, about 340 days), and renewing only by time left such
|
|
1297
|
+
// an account refused on every request for months. It is
|
|
1298
|
+
// { a }: a sign-in with another access token, to send the request with
|
|
1299
|
+
// once more;
|
|
1300
|
+
// { why, kind }: none to be had, why, and of what kind: "refresh
|
|
1301
|
+
// refused" (WorkBuddy turned the refresh token away too), "nothing to
|
|
1302
|
+
// renew with" (no refresh token, or one past its end), or "unknown"
|
|
1303
|
+
// (the refresh failed for another reason, the desktop's token is the
|
|
1304
|
+
// one refused).
|
|
1305
|
+
// began is the access token the request read at its start: a stored one
|
|
1306
|
+
// that isn't it was saved since by someone else (magpie's renewal,
|
|
1307
|
+
// another of its processes), and is taken rather than renewing again.
|
|
1308
|
+
//
|
|
1309
|
+
// A desktop sign-in is the app's: its file is read again, and what was
|
|
1310
|
+
// renewed here (desktopHeld) is let go, but its refresh token is never
|
|
1311
|
+
// spent for a refusal, which would sign the app itself out (see
|
|
1312
|
+
// desktopSignIn).
|
|
1313
|
+
async function reissued(site, client, getAuth, a, began) {
|
|
1314
|
+
if (a.source === "desktop") {
|
|
1315
|
+
desktopHeld.delete(site.id + "|" + a.uid)
|
|
1316
|
+
const d = readDesktop(site)
|
|
1317
|
+
if (!d) return { why: notDesktop(site), kind: "unknown" }
|
|
1318
|
+
if (d.access !== a.access) {
|
|
1319
|
+
desktopHeld.set(site.id + "|" + d.uid, d)
|
|
1320
|
+
return { a: d }
|
|
1321
|
+
}
|
|
1322
|
+
return { why: `${site.name} desktop's own sign-in is the one refused`, kind: "unknown" }
|
|
1323
|
+
}
|
|
1324
|
+
const newer = async () => {
|
|
1325
|
+
const now = await getAuth().catch(() => null)
|
|
1326
|
+
return now?.type === "oauth" && now.source !== "desktop" && now.access && now.access !== began && now.access !== a.access ? now : null
|
|
1327
|
+
}
|
|
1328
|
+
const saved = await newer()
|
|
1329
|
+
if (saved) return { a: saved }
|
|
1330
|
+
if (!refreshable(a)) return { why: a.refresh ? "its refresh token has ended" : "there is no refresh token to renew it with", kind: "nothing to renew with" }
|
|
1331
|
+
try {
|
|
1332
|
+
const next = await renew(site, a, async (n) => {
|
|
1333
|
+
try {
|
|
1334
|
+
await client?.auth?.set?.({ path: { id: site.id }, body: n })
|
|
1335
|
+
} catch {}
|
|
1336
|
+
})
|
|
1337
|
+
return next.access !== a.access ? { a: next } : { why: "renewing it gave the same token", kind: "unknown" }
|
|
1338
|
+
} catch (e) {
|
|
1339
|
+
if (refreshRefused(e)) {
|
|
1340
|
+
// spent by someone else, who saved what it gave
|
|
1341
|
+
const saved = await newer()
|
|
1342
|
+
if (saved) return { a: saved }
|
|
1343
|
+
return { why: `it refused the refresh token too (${e.said ?? e.message})`, kind: "refresh refused" }
|
|
1344
|
+
}
|
|
1345
|
+
return { why: `renewing it failed: ${e?.said ?? e?.message ?? e}`, kind: "unknown" }
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// gone says whether a sign-in reissued (r) couldn't renew is gone, on
|
|
1350
|
+
// WorkBuddy's word: it turned the refresh token away, or refused the access
|
|
1351
|
+
// token itself (tokenRefused, invalidToken) with none to renew it with. A
|
|
1352
|
+
// desktop sign-in never is: the app's file is read on each request, and
|
|
1353
|
+
// signing in to the app again is all it takes.
|
|
1354
|
+
const gone = (a, r, tokenRefused) =>
|
|
1355
|
+
a.source !== "desktop" && (r.kind === "refresh refused" || (r.kind === "nothing to renew with" && !!tokenRefused))
|
|
1356
|
+
|
|
1357
|
+
// refusedText says WorkBuddy refused a's sign-in, why it wasn't renewed,
|
|
1358
|
+
// and what to do.
|
|
1359
|
+
function refusedText(site, a, why, note) {
|
|
1360
|
+
return (
|
|
1361
|
+
`${site.name} refused this account's sign-in (401)${why ? "; " + why : ""}. ${note ? note + " " : ""}` +
|
|
1362
|
+
(a.source === "desktop"
|
|
1363
|
+
? `Sign in to ${site.name} desktop again; magpie reads its sign-in on the next request.`
|
|
1364
|
+
: `Sign in to it again in magpie: Providers → ${site.name} → this account's Sign in again.`)
|
|
1365
|
+
)
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
// signedOut is the answer to a request WorkBuddy refused this account's
|
|
1369
|
+
// sign-in for, renewed or not: a 401 that says so and what to do, which
|
|
1370
|
+
// WorkBuddy's own (an HTML page, its {code, msg}) doesn't. expired marks
|
|
1371
|
+
// the account in magpie (X-Magpie-Sign-In), out of routing and "sign in
|
|
1372
|
+
// again" on its row, till it is signed in again; else it is kept.
|
|
1373
|
+
function signedOut(site, a, why, expired, note) {
|
|
1374
|
+
const message = refusedText(site, a, why, note)
|
|
1253
1375
|
return new Response(JSON.stringify({ error: { message, type: "authentication_error", code: 401 } }), {
|
|
1254
1376
|
status: 401,
|
|
1255
1377
|
statusText: "Unauthorized",
|
|
1256
|
-
headers: { "content-type": "application/json" },
|
|
1378
|
+
headers: { "content-type": "application/json", "X-Magpie-Sign-In": expired ? "expired" : "kept" },
|
|
1257
1379
|
})
|
|
1258
1380
|
}
|
|
1259
1381
|
|
|
@@ -1267,9 +1389,12 @@ async function meter(site, a, path, body = {}) {
|
|
|
1267
1389
|
} catch {}
|
|
1268
1390
|
const code = Number.parseInt(env.code, 10) || 0
|
|
1269
1391
|
if (!res.ok) {
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1392
|
+
// the status and whether the token itself was refused go with it, for
|
|
1393
|
+
// usage's one renewal on a 401 (reissued)
|
|
1394
|
+
const said = { status: res.status, invalidToken: invalidToken(res) }
|
|
1395
|
+
if (code) throw Object.assign(new Error(env.msg || `error ${code}`), said)
|
|
1396
|
+
if (env.msg) throw Object.assign(new Error(env.msg), said)
|
|
1397
|
+
throw Object.assign(new Error(statusText(res.status)), said)
|
|
1273
1398
|
}
|
|
1274
1399
|
if (code) throw new Error(env.msg || `error ${code}`)
|
|
1275
1400
|
return env.data ?? null
|
|
@@ -1671,9 +1796,9 @@ function makePlugin(site) {
|
|
|
1671
1796
|
baseURL: api(site),
|
|
1672
1797
|
apiKey: "",
|
|
1673
1798
|
async fetch(input, init) {
|
|
1674
|
-
|
|
1675
|
-
if (
|
|
1676
|
-
a = await current(site, client,
|
|
1799
|
+
const stored = await getAuth()
|
|
1800
|
+
if (stored?.type !== "oauth") throw new Error(`${site.name} isn't signed in`)
|
|
1801
|
+
let a = await current(site, client, stored)
|
|
1677
1802
|
const req = input instanceof Request ? input : null
|
|
1678
1803
|
const headers = new Headers(init?.headers ?? req?.headers)
|
|
1679
1804
|
headers.delete("authorization")
|
|
@@ -1687,8 +1812,44 @@ function makePlugin(site) {
|
|
|
1687
1812
|
const fit = fitted(body)
|
|
1688
1813
|
body = normalizeToolChoice(fit.body)
|
|
1689
1814
|
attend(headers, body, session)
|
|
1690
|
-
const
|
|
1815
|
+
const post = (b) =>
|
|
1691
1816
|
fetch(req ? req.url : input, { ...init, method: init?.method ?? req?.method, headers, body: b }).catch((e) => unanswered(site, a, b, e, init?.signal))
|
|
1817
|
+
// a 401 is answered once, on whichever try meets it: by a
|
|
1818
|
+
// sign-in with another access token (reissued), the request
|
|
1819
|
+
// sent once more with it, never again after that; else, or
|
|
1820
|
+
// when that is refused too, by signedOut. The body is a
|
|
1821
|
+
// string here (or bytes), which goes again as it is; one that
|
|
1822
|
+
// can't be read twice isn't sent again.
|
|
1823
|
+
//
|
|
1824
|
+
// The account is marked (expired) only on WorkBuddy's word
|
|
1825
|
+
// that the sign-in is gone: it turned the refresh token away,
|
|
1826
|
+
// or refused the access token itself (invalidToken) with none
|
|
1827
|
+
// to renew it with, or refused even the token just renewed. A
|
|
1828
|
+
// desktop sign-in is never marked: the app's file is read on
|
|
1829
|
+
// each request, so signing in to the app again is enough.
|
|
1830
|
+
let renewed = false
|
|
1831
|
+
const ours = new WeakSet() // the answers signedOut gave, passed on as they are
|
|
1832
|
+
const out = (why, expired, res) => {
|
|
1833
|
+
const r = signedOut(site, a, why, expired && a.source !== "desktop", edgeNotes.get(res))
|
|
1834
|
+
ours.add(r)
|
|
1835
|
+
return r
|
|
1836
|
+
}
|
|
1837
|
+
const resendable = (b) => b == null || typeof b === "string" || b instanceof ArrayBuffer || ArrayBuffer.isView(b)
|
|
1838
|
+
const send = async (b) => {
|
|
1839
|
+
const res = await post(b)
|
|
1840
|
+
if (res.status !== 401) return res
|
|
1841
|
+
if (renewed) return out("it refused the token renewed for it too", invalidToken(res), res)
|
|
1842
|
+
if (!resendable(b)) return res
|
|
1843
|
+
await res.body?.cancel().catch(() => {})
|
|
1844
|
+
const r = await reissued(site, client, getAuth, a, stored.access)
|
|
1845
|
+
if (!r.a) return out(r.why, gone(a, r, invalidToken(res)), res)
|
|
1846
|
+
renewed = true
|
|
1847
|
+
a = r.a
|
|
1848
|
+
sign(site, a, headers)
|
|
1849
|
+
const again = await post(b)
|
|
1850
|
+
if (again.status !== 401) return again
|
|
1851
|
+
return out("it refused the token renewed for it too", invalidToken(again), again)
|
|
1852
|
+
}
|
|
1692
1853
|
// a model known to turn tool results then the user's message away
|
|
1693
1854
|
// gets the empty reply between them up front (see endsAfterTools)
|
|
1694
1855
|
const model = chatOf(body)?.model
|
|
@@ -1716,20 +1877,43 @@ function makePlugin(site) {
|
|
|
1716
1877
|
res = new Response(text, { status: res.status, statusText: res.statusText, headers: res.headers })
|
|
1717
1878
|
}
|
|
1718
1879
|
}
|
|
1719
|
-
|
|
1880
|
+
if (ours.has(res)) return res
|
|
1881
|
+
// renewed here: a mark the account had comes off
|
|
1882
|
+
return res.status >= 400 ? explained(res) : kept(await renamed(res, fit.names), renewed ? "renewed" : "kept")
|
|
1720
1883
|
},
|
|
1721
1884
|
}
|
|
1722
1885
|
},
|
|
1723
1886
|
// the account's credits, as magpie's built-in showed them; each
|
|
1724
1887
|
// read keeps the sign-in (signIn), as the built-in's neither marked
|
|
1725
|
-
// an account lapsed nor cleared it
|
|
1888
|
+
// an account lapsed nor cleared it, but for a 401 (gone, below)
|
|
1726
1889
|
async usage(getAuth) {
|
|
1727
1890
|
const auth = await getAuth()
|
|
1728
1891
|
if (auth?.type !== "oauth" || !(auth.access || auth.source)) return { error: "not signed in", signIn: "kept" }
|
|
1729
1892
|
try {
|
|
1730
|
-
|
|
1893
|
+
let a = await current(site, client, auth)
|
|
1731
1894
|
// the meter has no /v2 prefix, on either site
|
|
1732
|
-
const
|
|
1895
|
+
const SUMMARY = "/billing/meter/get-user-resource-summary"
|
|
1896
|
+
// a 401 is answered as a chat's is: once, by a sign-in with
|
|
1897
|
+
// another access token, else the card says WorkBuddy refused
|
|
1898
|
+
// the sign-in and what to do; renewed, a mark comes off
|
|
1899
|
+
let said = "kept"
|
|
1900
|
+
let sum
|
|
1901
|
+
try {
|
|
1902
|
+
sum = await meter(site, a, SUMMARY)
|
|
1903
|
+
} catch (e) {
|
|
1904
|
+
if (e?.status !== 401) throw e
|
|
1905
|
+
const r = await reissued(site, client, getAuth, a, auth.access)
|
|
1906
|
+
if (!r.a) return { error: refusedText(site, a, r.why), signIn: gone(a, r, e.invalidToken) ? "expired" : "kept" }
|
|
1907
|
+
a = r.a
|
|
1908
|
+
try {
|
|
1909
|
+
sum = await meter(site, a, SUMMARY)
|
|
1910
|
+
} catch (e) {
|
|
1911
|
+
if (e?.status !== 401) throw e
|
|
1912
|
+
const why = "it refused the token renewed for it too"
|
|
1913
|
+
return { error: refusedText(site, a, why), signIn: a.source !== "desktop" && e.invalidToken ? "expired" : "kept" }
|
|
1914
|
+
}
|
|
1915
|
+
said = "renewed"
|
|
1916
|
+
}
|
|
1733
1917
|
const out = usageOf(sum, auth.plan)
|
|
1734
1918
|
// an enterprise account's summary has no packages: its credits
|
|
1735
1919
|
// are the enterprise meter's (#1904). A personal account's
|
|
@@ -1740,7 +1924,7 @@ function makePlugin(site) {
|
|
|
1740
1924
|
try {
|
|
1741
1925
|
ent = enterpriseUsageOf(await meter(site, a, "/billing/meter/get-enterprise-user-usage"))
|
|
1742
1926
|
} catch {}
|
|
1743
|
-
if (ent) return { plan: auth.plan || "Enterprise", windows: await spareFree(site, a, [ent]), signIn:
|
|
1927
|
+
if (ent) return { plan: auth.plan || "Enterprise", windows: await spareFree(site, a, [ent]), signIn: said }
|
|
1744
1928
|
}
|
|
1745
1929
|
// when the credits left run out, for routing to spend them first;
|
|
1746
1930
|
// a list that can't be read leaves it unknown, the card stands
|
|
@@ -1751,7 +1935,7 @@ function makePlugin(site) {
|
|
|
1751
1935
|
} catch {}
|
|
1752
1936
|
}
|
|
1753
1937
|
await spareFree(site, a, out.windows)
|
|
1754
|
-
return { ...out, signIn:
|
|
1938
|
+
return { ...out, signIn: said }
|
|
1755
1939
|
} catch (e) {
|
|
1756
1940
|
return { error: e?.message ?? String(e), signIn: "kept" }
|
|
1757
1941
|
}
|
|
@@ -1806,17 +1990,19 @@ const EXHAUSTED_HINT =
|
|
|
1806
1990
|
// another error, and insufficient_quota would rest the account as out of credit.
|
|
1807
1991
|
const EXHAUSTED_TEXT = /credits exhausted|额度已用尽|["']code["']\s*:\s*["']?14018(?![\d.])/i
|
|
1808
1992
|
|
|
1809
|
-
// kept is an answer that went through, saying the sign-in is kept
|
|
1810
|
-
// built-in never cleared a WorkBuddy account's mark, as it never set one
|
|
1811
|
-
|
|
1993
|
+
// kept is an answer that went through, saying the sign-in is kept (the
|
|
1994
|
+
// built-in never cleared a WorkBuddy account's mark, as it never set one),
|
|
1995
|
+
// or "renewed" when it went through on a sign-in renewed for a 401, which
|
|
1996
|
+
// takes a mark off (see signedOut).
|
|
1997
|
+
function kept(res, said = "kept") {
|
|
1812
1998
|
const headers = new Headers(res.headers)
|
|
1813
|
-
headers.set("X-Magpie-Sign-In",
|
|
1999
|
+
headers.set("X-Magpie-Sign-In", said)
|
|
1814
2000
|
return new Response(res.body, { status: res.status, statusText: res.statusText, headers })
|
|
1815
2001
|
}
|
|
1816
2002
|
|
|
1817
2003
|
// explained is res with REFUSED_HINT, RESTRICTED_HINT or EXHAUSTED_HINT added to that refusal's message.
|
|
1818
|
-
// Each refusal says the sign-in is kept:
|
|
1819
|
-
//
|
|
2004
|
+
// Each refusal says the sign-in is kept: a 401 never comes here, it is
|
|
2005
|
+
// signedOut's.
|
|
1820
2006
|
async function explained(res) {
|
|
1821
2007
|
const text = await res.text()
|
|
1822
2008
|
const again = (b) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magpie-community/opencode-workbuddy-auth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"description": "OpenCode provider plugins for WorkBuddy's plans: WorkBuddy (CodeBuddy, copilot.tencent.com) and WorkBuddy AI (www.workbuddy.ai)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|