@magpie-community/opencode-workbuddy-auth 0.1.27 → 0.1.29
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 +12 -1
- package/index.mjs +85 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,7 +85,18 @@ default one, as the app does.
|
|
|
85
85
|
|
|
86
86
|
WorkBuddy refuses some agents' identity lines as an "unapproved channel"
|
|
87
87
|
(11128), so those lines are reworded before a chat goes out. Since 0.1.27
|
|
88
|
-
(#279) that includes DeepSeek Harness's opening line.
|
|
88
|
+
(#279) that includes DeepSeek Harness's opening line. A refusal of words
|
|
89
|
+
not reworded yet is sent once more, since 0.1.29, with the system prompt
|
|
90
|
+
moved into the first user message (inside `<instructions>` tags) and
|
|
91
|
+
WorkBuddy's default system prompt in its place: WorkBuddy checks only the
|
|
92
|
+
system and assistant messages, and the line it matched is then in neither.
|
|
93
|
+
A system message later in the chat becomes a user message where it stands.
|
|
94
|
+
The plugin remembers each refused system prompt while it is loaded, so that
|
|
95
|
+
agent's next chats go moved at once. A refusal that stands after the move
|
|
96
|
+
(words in an assistant message) ends in a hint that names no one client
|
|
97
|
+
(since 0.1.28): WorkBuddy refused words in this client's prompt; use it from
|
|
98
|
+
Hermes, OpenCode or Pi, or add another provider to the group. Neither
|
|
99
|
+
refusal rests the account.
|
|
89
100
|
|
|
90
101
|
The loader also translates OpenAI-compatible request spellings WorkBuddy
|
|
91
102
|
doesn't take: `max_completion_tokens` becomes `max_tokens`, `developer`
|
package/index.mjs
CHANGED
|
@@ -542,6 +542,61 @@ function withSystem(body) {
|
|
|
542
542
|
}
|
|
543
543
|
}
|
|
544
544
|
|
|
545
|
+
// WorkBuddy's channel check reads the system and assistant messages only:
|
|
546
|
+
// tried on 2026-10-21 with real accounts on both builds (glm-5.3,
|
|
547
|
+
// deepseek-v4.1-flash, gpt-5.5), each refused line was served in the user's
|
|
548
|
+
// message, a tool's result and a user message's parts, and refused again in
|
|
549
|
+
// the system or an assistant message. It matches a known line whole and
|
|
550
|
+
// exactly (one extra space lets it through), so FLAGGED only ever knows the
|
|
551
|
+
// lines met so far, and another agent's opening line is refused until one is
|
|
552
|
+
// added (Wing on Discord, 10-21, with an agent we couldn't name).
|
|
553
|
+
//
|
|
554
|
+
// systemAsUser is body with its system prompt read to the model as the
|
|
555
|
+
// first thing the user says, in INSTRUCTIONS' tags, and WorkBuddy's default
|
|
556
|
+
// system prompt first, as both builds want a system message there (11128
|
|
557
|
+
// "first message is not system prompt"). A system message later in the chat
|
|
558
|
+
// becomes the user's where it stands. null when there's nothing to move.
|
|
559
|
+
const DEFAULT_SYSTEM = "You are a helpful assistant."
|
|
560
|
+
const INSTRUCTIONS = ["<instructions>\n", "\n</instructions>"]
|
|
561
|
+
|
|
562
|
+
function textOf(content) {
|
|
563
|
+
if (typeof content === "string") return content
|
|
564
|
+
if (!Array.isArray(content)) return ""
|
|
565
|
+
return content.map((p) => (typeof p?.text === "string" ? p.text : "")).filter(Boolean).join("\n")
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// systemKey is the text of a chat's system messages, which a chat refused
|
|
569
|
+
// once is known by: the same agent's next chat goes with systemAsUser at once
|
|
570
|
+
function systemKey(body) {
|
|
571
|
+
const msgs = chatOf(body)?.messages
|
|
572
|
+
if (!Array.isArray(msgs)) return ""
|
|
573
|
+
const key = msgs.filter((m) => m?.role === "system").map((m) => textOf(m.content)).join("\n")
|
|
574
|
+
return key.trim() === DEFAULT_SYSTEM ? "" : key
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function systemAsUser(body) {
|
|
578
|
+
const b = chatOf(body)
|
|
579
|
+
const msgs = b?.messages
|
|
580
|
+
if (!Array.isArray(msgs)) return null
|
|
581
|
+
let i = 0
|
|
582
|
+
const lead = []
|
|
583
|
+
for (; i < msgs.length && msgs[i]?.role === "system"; i++) lead.push(textOf(msgs[i].content))
|
|
584
|
+
const said = lead.join("\n\n").trim()
|
|
585
|
+
const rest = msgs.slice(i).map((m) => (m?.role === "system" ? { ...m, role: "user" } : m))
|
|
586
|
+
const moved = said && said !== DEFAULT_SYSTEM
|
|
587
|
+
if (!moved && rest.every((m, k) => m === msgs[i + k])) return null
|
|
588
|
+
if (moved) {
|
|
589
|
+
const text = INSTRUCTIONS[0] + said + INSTRUCTIONS[1]
|
|
590
|
+
const u = rest.findIndex((m) => m?.role === "user")
|
|
591
|
+
if (u < 0) rest.unshift({ role: "user", content: text })
|
|
592
|
+
else if (typeof rest[u].content === "string") rest[u] = { ...rest[u], content: text + "\n\n" + rest[u].content }
|
|
593
|
+
else if (Array.isArray(rest[u].content)) rest[u] = { ...rest[u], content: [{ type: "text", text }, ...rest[u].content] }
|
|
594
|
+
else rest[u] = { ...rest[u], content: text }
|
|
595
|
+
}
|
|
596
|
+
b.messages = [{ role: "system", content: DEFAULT_SYSTEM }, ...rest]
|
|
597
|
+
return JSON.stringify(b)
|
|
598
|
+
}
|
|
599
|
+
|
|
545
600
|
// normalizeToolChoice converts the object form emitted by OpenAI clients to
|
|
546
601
|
// WorkBuddy's string form. It runs after fitted(), so a named choice follows a
|
|
547
602
|
// tool name that was made safe for WorkBuddy.
|
|
@@ -1559,6 +1614,9 @@ function makePlugin(site) {
|
|
|
1559
1614
|
// the models of this site that refused tool results then the user's
|
|
1560
1615
|
// message, for this run: their next chats go with replyAfterTools at once
|
|
1561
1616
|
const endsAfterTools = new Set()
|
|
1617
|
+
// the system prompts this run WorkBuddy refused as an unapproved channel:
|
|
1618
|
+
// their next chats go with systemAsUser at once, not refused first
|
|
1619
|
+
const refusedSystems = new Set()
|
|
1562
1620
|
return async ({ client }) => ({
|
|
1563
1621
|
config: async (config) => {
|
|
1564
1622
|
config.provider ??= {}
|
|
@@ -1629,15 +1687,27 @@ function makePlugin(site) {
|
|
|
1629
1687
|
// a model known to turn tool results then the user's message away
|
|
1630
1688
|
// gets the empty reply between them up front (see endsAfterTools)
|
|
1631
1689
|
const model = chatOf(body)?.model
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1690
|
+
// a system prompt WorkBuddy refused before goes as the user's
|
|
1691
|
+
// words at once (see systemAsUser)
|
|
1692
|
+
const sysKey = systemKey(body)
|
|
1693
|
+
if (sysKey && refusedSystems.has(sysKey)) body = systemAsUser(body) ?? body
|
|
1694
|
+
const shaped = (b) => (endsAfterTools.has(model) ? replyAfterTools(b) ?? b : b)
|
|
1695
|
+
let res = await send(shaped(body))
|
|
1696
|
+
if (res.status === 400) {
|
|
1697
|
+
let text = await res.text()
|
|
1698
|
+
const moved = REFUSED.test(text) && sysKey && !refusedSystems.has(sysKey) ? systemAsUser(body) : null
|
|
1699
|
+
if (moved) {
|
|
1700
|
+
refusedSystems.add(sysKey)
|
|
1701
|
+
if (refusedSystems.size > 256) refusedSystems.delete(refusedSystems.values().next().value)
|
|
1702
|
+
body = moved
|
|
1703
|
+
res = await send(shaped(body))
|
|
1704
|
+
if (res.status === 400) text = await res.text()
|
|
1705
|
+
}
|
|
1706
|
+
const again = res.status === 400 && !endsAfterTools.has(model) && AFTER_TOOLS_REFUSED.test(text) ? replyAfterTools(body) : null
|
|
1637
1707
|
if (again) {
|
|
1638
1708
|
endsAfterTools.add(model)
|
|
1639
1709
|
res = await send(again)
|
|
1640
|
-
} else {
|
|
1710
|
+
} else if (res.status === 400) {
|
|
1641
1711
|
res = new Response(text, { status: res.status, statusText: res.statusText, headers: res.headers })
|
|
1642
1712
|
}
|
|
1643
1713
|
}
|
|
@@ -1699,11 +1769,15 @@ function makePlugin(site) {
|
|
|
1699
1769
|
|
|
1700
1770
|
// REFUSED_HINT is what the user can do about WorkBuddy's "Illegal API
|
|
1701
1771
|
// invocation from an unapproved channel": both builds answer it to a chat
|
|
1702
|
-
// holding words
|
|
1703
|
-
//
|
|
1704
|
-
//
|
|
1772
|
+
// holding words they turn away, whatever the headers. Codex's and Claude
|
|
1773
|
+
// Code's were the first (magpie #182), DeepSeek Harness's and Claude
|
|
1774
|
+
// Desktop's came later (#279, #170), and WorkBuddy reads every message, so
|
|
1775
|
+
// it names no client. FLAGGED rewrites the words known; this is for ones it
|
|
1776
|
+
// doesn't know. It ends the error's message word for word as magpie's
|
|
1777
|
+
// Routing page has it (WB_REFUSED in its routing client.js), which says it
|
|
1778
|
+
// apart from WorkBuddy's own words.
|
|
1705
1779
|
const REFUSED_HINT =
|
|
1706
|
-
"WorkBuddy
|
|
1780
|
+
"WorkBuddy refused words in this client's prompt (most often its system prompt); use it from Hermes, OpenCode or Pi, or add another provider to this group"
|
|
1707
1781
|
const REFUSED = /unapproved channel|illegal api invocation/i
|
|
1708
1782
|
|
|
1709
1783
|
// RESTRICTED_HINT is what the user can do about WorkBuddy's 403 code 11140
|
|
@@ -1781,4 +1855,4 @@ export const WorkBuddyAuthPlugin = makePlugin(SITES.workbuddy)
|
|
|
1781
1855
|
export const WorkBuddyAIAuthPlugin = makePlugin(SITES["workbuddy-ai"])
|
|
1782
1856
|
|
|
1783
1857
|
// for tests
|
|
1784
|
-
export const _internal = { EDGE_READS, fitShape, replyAfterTools, AFTER_TOOLS_REFUSED, enterpriseUsageOf, 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, priceOf, fresh, SITES, renewing, renewed, SESSION, attend, turnKey, conversationKey, signatureOf }
|
|
1858
|
+
export const _internal = { systemAsUser, systemKey, DEFAULT_SYSTEM, EDGE_READS, fitShape, replyAfterTools, AFTER_TOOLS_REFUSED, enterpriseUsageOf, 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, priceOf, 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.
|
|
3
|
+
"version": "0.1.29",
|
|
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",
|