@magpie-community/opencode-workbuddy-auth 0.1.16 → 0.1.18

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 (3) hide show
  1. package/README.md +2 -1
  2. package/index.mjs +35 -9
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -24,7 +24,8 @@ The package exports `WorkBuddyAuthPlugin` (`workbuddy`) and
24
24
  - Windows: `~/AppData/Local/CodeBuddyExtension/…`
25
25
  - Linux: `~/.local/share/CodeBuddyExtension/…`
26
26
 
27
- A sign-in the app keeps encrypted can't be read, and this way fails.
27
+ WorkBuddy 5.6 and later keep the sign-in encrypted, which only the app
28
+ opens, so this way fails there and says so: use the browser sign-in.
28
29
 
29
30
  A browser sign-in is kept as an OpenCode `oauth` auth: the access and
30
31
  refresh tokens, their expiry, the user id and the domain; desktop's as a
package/index.mjs CHANGED
@@ -931,6 +931,19 @@ function readDesktop(site) {
931
931
  }
932
932
  }
933
933
 
934
+ // notDesktop says why there is no desktop sign-in to use: WorkBuddy 5.6
935
+ // and later seal its tokens at rest ({$wbEncrypted: 1, envelope}), which
936
+ // only the app itself opens (plugins#18), so the browser sign-in is the way;
937
+ // else the app isn't signed in.
938
+ function notDesktop(site) {
939
+ try {
940
+ const t = JSON.parse(readFileSync(desktopFile(site), "utf8"))?.auth ?? {}
941
+ if (t.accessToken?.$wbEncrypted || t.refreshToken?.$wbEncrypted)
942
+ return `${site.name} desktop keeps its sign-in encrypted (WorkBuddy 5.6 and later), which magpie can't read; sign in with "${site.name} account (browser)" instead`
943
+ } catch {}
944
+ return `${site.name} desktop isn't signed in`
945
+ }
946
+
934
947
  // desktopSignIn uses the account WorkBuddy desktop is signed in to: nothing
935
948
  // is copied (a copied refresh token would be the app's and the plugin's
936
949
  // both, and the first to use it would sign the other out); its file is
@@ -942,7 +955,7 @@ function desktopSignIn(site) {
942
955
  method: "auto",
943
956
  async callback() {
944
957
  const d = readDesktop(site)
945
- if (!d) return { type: "failed", error: `${site.name} desktop isn't signed in` }
958
+ if (!d) return { type: "failed", error: notDesktop(site) }
946
959
  return { type: "success", refresh: "", access: "", expires: 0, source: "desktop", accountId: d.name || site.name, uid: d.uid }
947
960
  },
948
961
  }
@@ -957,7 +970,7 @@ const desktopHeld = new Map()
957
970
  async function current(site, client, auth) {
958
971
  if (auth?.source !== "desktop") return fresh(site, client, auth)
959
972
  const d = readDesktop(site)
960
- if (!d) throw new Error(`${site.name} desktop isn't signed in`)
973
+ if (!d) throw new Error(notDesktop(site))
961
974
  const k = site.id + "|" + d.uid
962
975
  const h = desktopHeld.get(k)
963
976
  const a = await fresh(site, null, h && h.expires >= d.expires ? h : d)
@@ -1350,6 +1363,18 @@ const REFUSED_HINT =
1350
1363
  "WorkBuddy refuses chats from Codex and Claude Code (their system prompt); use it from Hermes, OpenCode or Pi, or add another provider to this group"
1351
1364
  const REFUSED = /unapproved channel|illegal api invocation/i
1352
1365
 
1366
+ // RESTRICTED_HINT is what the user can do about WorkBuddy's 403 code 11140
1367
+ // "request illegal", whose displayMsg says "Your account has been restricted
1368
+ // due to unusual activity" (magpie #1849): the account is out until WorkBuddy
1369
+ // lifts the restriction, which an appeal asks for. It ends the error's
1370
+ // message as magpie's built-in puts it (provider.WBRestrictedHint), word for
1371
+ // word, so magpie rests the account for hours rather than minutes.
1372
+ const RESTRICTED_HINT =
1373
+ "WorkBuddy restricted this account for unusual activity; it stays out until WorkBuddy lifts that, which an appeal (Submit feedback in WorkBuddy) asks for"
1374
+ const RESTRICTED_CODE = 11140
1375
+ // The code is matched whole, as with 14018: 111400 is another error.
1376
+ const RESTRICTED_TEXT = /account has been restricted due to unusual activity|账号存在异常行为|帳號存在異常行為|["']code["']\s*:\s*["']?11140(?![\d.])/i
1377
+
1353
1378
  // EXHAUSTED_HINT explains WorkBuddy's requirement of an active package
1354
1379
  // even for 0-rate / free models. When an account has no packages or 0 credits,
1355
1380
  // WorkBuddy returns code 14018 ("Credits exhausted...").
@@ -1367,7 +1392,7 @@ function kept(res) {
1367
1392
  return new Response(res.body, { status: res.status, statusText: res.statusText, headers })
1368
1393
  }
1369
1394
 
1370
- // explained is res with REFUSED_HINT or EXHAUSTED_HINT added to that refusal's message.
1395
+ // explained is res with REFUSED_HINT, RESTRICTED_HINT or EXHAUSTED_HINT added to that refusal's message.
1371
1396
  // Each refusal says the sign-in is kept: the built-in passed WorkBuddy's
1372
1397
  // 401 on and never marked a WorkBuddy account lapsed.
1373
1398
  async function explained(res) {
@@ -1386,8 +1411,9 @@ async function explained(res) {
1386
1411
  const at = v?.error && typeof v.error === "object" ? v.error : v && typeof v === "object" ? v : null
1387
1412
  const code = at?.data?.code ?? at?.code ?? v?.code ?? v?.data?.code
1388
1413
  const isRefused = REFUSED.test(text)
1389
- const isExhausted = code === 14018 || Number(code) === 14018 || EXHAUSTED_TEXT.test(text)
1390
- if (!isRefused && !isExhausted) return again(text)
1414
+ const isRestricted = !isRefused && (Number(code) === RESTRICTED_CODE || RESTRICTED_TEXT.test(text))
1415
+ const isExhausted = !isRestricted && (code === 14018 || Number(code) === 14018 || EXHAUSTED_TEXT.test(text))
1416
+ if (!isRefused && !isRestricted && !isExhausted) return again(text)
1391
1417
 
1392
1418
  let msg = text.trim()
1393
1419
  if (at && typeof at.message === "string") msg = at.message
@@ -1396,9 +1422,9 @@ async function explained(res) {
1396
1422
  else if (typeof v?.msg === "string" && v.msg) msg = v.msg // WorkBuddy's own {"code", "msg"}
1397
1423
  else if (typeof v?.data?.msg === "string") msg = v.data.msg
1398
1424
 
1399
- const hint = isRefused ? REFUSED_HINT : EXHAUSTED_HINT
1400
- const type = isRefused ? "permission_error" : "insufficient_quota"
1401
- const errCode = isRefused ? null : 14018
1425
+ const hint = isRefused ? REFUSED_HINT : isRestricted ? RESTRICTED_HINT : EXHAUSTED_HINT
1426
+ const type = isRefused || isRestricted ? "permission_error" : "insufficient_quota"
1427
+ const errCode = isRefused ? null : isRestricted ? RESTRICTED_CODE : 14018
1402
1428
  msg = `${msg} — ${hint}`
1403
1429
  const headers = new Headers(res.headers)
1404
1430
  headers.set("content-type", "application/json")
@@ -1412,4 +1438,4 @@ export const WorkBuddyAuthPlugin = makePlugin(SITES.workbuddy)
1412
1438
  export const WorkBuddyAIAuthPlugin = makePlugin(SITES["workbuddy-ai"])
1413
1439
 
1414
1440
  // for tests
1415
- export const _internal = { textOnly, runsOutOf, packagesOf, endOf, enterpriseHeaders, enterpriseOf, liveModels, browserSignIn, withSystem, normalizeToolChoice, fitTools, fitted, renamed, unflagged, usageOf, desktopHeld, explained, REFUSED_HINT, EXHAUSTED_HINT, freeCredits, creditsOf, fresh, SITES, renewing, renewed, SESSION, attend, turnKey, conversationKey, signatureOf }
1441
+ export const _internal = { notDesktop, textOnly, runsOutOf, packagesOf, endOf, enterpriseHeaders, enterpriseOf, liveModels, browserSignIn, withSystem, normalizeToolChoice, fitTools, fitted, renamed, unflagged, usageOf, desktopHeld, explained, REFUSED_HINT, RESTRICTED_HINT, EXHAUSTED_HINT, freeCredits, creditsOf, fresh, SITES, renewing, renewed, SESSION, attend, turnKey, conversationKey, signatureOf }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-workbuddy-auth",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
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",