@magpie-community/opencode-workbuddy-auth 0.1.28 → 0.1.30

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 +14 -3
  2. package/index.mjs +82 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -86,9 +86,20 @@ default one, as the app does.
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
88
  (#279) that includes DeepSeek Harness's opening line. A refusal of words
89
- not reworded yet ends in a hint that names no one client (since 0.1.28):
90
- WorkBuddy refused words in this client's prompt; use it from Hermes,
91
- OpenCode or Pi, or add another provider to the group.
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. The move can't reach an assistant
96
+ message, so words the model quotes back into one keep that chat refused
97
+ until they are in the list reworded in every message (since 0.1.30, the "Web GUI whose URL the runtime
98
+ context names" line, #291); otherwise only a new chat clears it. A refusal
99
+ that stands after the move ends in a hint that names no one client
100
+ (since 0.1.28): WorkBuddy refused words in this client's prompt; use it from
101
+ Hermes, OpenCode or Pi, or add another provider to the group. Neither
102
+ refusal rests the account.
92
103
 
93
104
  The loader also translates OpenAI-compatible request spellings WorkBuddy
94
105
  doesn't take: `max_completion_tokens` becomes `max_tokens`, `developer`
package/index.mjs CHANGED
@@ -467,6 +467,11 @@ const FLAGGED = [
467
467
  // WorkBuddy refuses as 11128 (magpie-community/plugins#279; the same
468
468
  // refusal traced in smart-open/TraeWorkAssistant#125).
469
469
  [/You are an AI agent powered by DeepSeek Harness\.?/gi, "You are an AI agent that helps the user with programming tasks."],
470
+ // A line WorkBuddy refuses in a long DeepSeek Harness chat, measured 400
471
+ // then 200 with this wording by tearslee (magpie-community/plugins#291). The
472
+ // model can quote it back into an assistant message, which the move to
473
+ // the user's instructions can't reach, so it is reworded in every message.
474
+ [/Web GUI whose URL the runtime context names\.\s+When the user refers/gi, "Web GUI whose URL the runtime context names. The interface, when the user refers"],
470
475
  ]
471
476
 
472
477
  function unflagged(text) {
@@ -542,6 +547,61 @@ function withSystem(body) {
542
547
  }
543
548
  }
544
549
 
550
+ // WorkBuddy's channel check reads the system and assistant messages only:
551
+ // tried on 2026-10-21 with real accounts on both builds (glm-5.3,
552
+ // deepseek-v4.1-flash, gpt-5.5), each refused line was served in the user's
553
+ // message, a tool's result and a user message's parts, and refused again in
554
+ // the system or an assistant message. It matches a known line whole and
555
+ // exactly (one extra space lets it through), so FLAGGED only ever knows the
556
+ // lines met so far, and another agent's opening line is refused until one is
557
+ // added (Wing on Discord, 10-21, with an agent we couldn't name).
558
+ //
559
+ // systemAsUser is body with its system prompt read to the model as the
560
+ // first thing the user says, in INSTRUCTIONS' tags, and WorkBuddy's default
561
+ // system prompt first, as both builds want a system message there (11128
562
+ // "first message is not system prompt"). A system message later in the chat
563
+ // becomes the user's where it stands. null when there's nothing to move.
564
+ const DEFAULT_SYSTEM = "You are a helpful assistant."
565
+ const INSTRUCTIONS = ["<instructions>\n", "\n</instructions>"]
566
+
567
+ function textOf(content) {
568
+ if (typeof content === "string") return content
569
+ if (!Array.isArray(content)) return ""
570
+ return content.map((p) => (typeof p?.text === "string" ? p.text : "")).filter(Boolean).join("\n")
571
+ }
572
+
573
+ // systemKey is the text of a chat's system messages, which a chat refused
574
+ // once is known by: the same agent's next chat goes with systemAsUser at once
575
+ function systemKey(body) {
576
+ const msgs = chatOf(body)?.messages
577
+ if (!Array.isArray(msgs)) return ""
578
+ const key = msgs.filter((m) => m?.role === "system").map((m) => textOf(m.content)).join("\n")
579
+ return key.trim() === DEFAULT_SYSTEM ? "" : key
580
+ }
581
+
582
+ function systemAsUser(body) {
583
+ const b = chatOf(body)
584
+ const msgs = b?.messages
585
+ if (!Array.isArray(msgs)) return null
586
+ let i = 0
587
+ const lead = []
588
+ for (; i < msgs.length && msgs[i]?.role === "system"; i++) lead.push(textOf(msgs[i].content))
589
+ const said = lead.join("\n\n").trim()
590
+ const rest = msgs.slice(i).map((m) => (m?.role === "system" ? { ...m, role: "user" } : m))
591
+ const moved = said && said !== DEFAULT_SYSTEM
592
+ if (!moved && rest.every((m, k) => m === msgs[i + k])) return null
593
+ if (moved) {
594
+ const text = INSTRUCTIONS[0] + said + INSTRUCTIONS[1]
595
+ const u = rest.findIndex((m) => m?.role === "user")
596
+ if (u < 0) rest.unshift({ role: "user", content: text })
597
+ else if (typeof rest[u].content === "string") rest[u] = { ...rest[u], content: text + "\n\n" + rest[u].content }
598
+ else if (Array.isArray(rest[u].content)) rest[u] = { ...rest[u], content: [{ type: "text", text }, ...rest[u].content] }
599
+ else rest[u] = { ...rest[u], content: text }
600
+ }
601
+ b.messages = [{ role: "system", content: DEFAULT_SYSTEM }, ...rest]
602
+ return JSON.stringify(b)
603
+ }
604
+
545
605
  // normalizeToolChoice converts the object form emitted by OpenAI clients to
546
606
  // WorkBuddy's string form. It runs after fitted(), so a named choice follows a
547
607
  // tool name that was made safe for WorkBuddy.
@@ -1559,6 +1619,9 @@ function makePlugin(site) {
1559
1619
  // the models of this site that refused tool results then the user's
1560
1620
  // message, for this run: their next chats go with replyAfterTools at once
1561
1621
  const endsAfterTools = new Set()
1622
+ // the system prompts this run WorkBuddy refused as an unapproved channel:
1623
+ // their next chats go with systemAsUser at once, not refused first
1624
+ const refusedSystems = new Set()
1562
1625
  return async ({ client }) => ({
1563
1626
  config: async (config) => {
1564
1627
  config.provider ??= {}
@@ -1629,15 +1692,27 @@ function makePlugin(site) {
1629
1692
  // a model known to turn tool results then the user's message away
1630
1693
  // gets the empty reply between them up front (see endsAfterTools)
1631
1694
  const model = chatOf(body)?.model
1632
- const quiet = endsAfterTools.has(model) ? replyAfterTools(body) : null
1633
- let res = await send(quiet ?? body)
1634
- if (res.status === 400 && !quiet) {
1635
- const text = await res.text()
1636
- const again = AFTER_TOOLS_REFUSED.test(text) ? replyAfterTools(body) : null
1695
+ // a system prompt WorkBuddy refused before goes as the user's
1696
+ // words at once (see systemAsUser)
1697
+ const sysKey = systemKey(body)
1698
+ if (sysKey && refusedSystems.has(sysKey)) body = systemAsUser(body) ?? body
1699
+ const shaped = (b) => (endsAfterTools.has(model) ? replyAfterTools(b) ?? b : b)
1700
+ let res = await send(shaped(body))
1701
+ if (res.status === 400) {
1702
+ let text = await res.text()
1703
+ const moved = REFUSED.test(text) && sysKey && !refusedSystems.has(sysKey) ? systemAsUser(body) : null
1704
+ if (moved) {
1705
+ refusedSystems.add(sysKey)
1706
+ if (refusedSystems.size > 256) refusedSystems.delete(refusedSystems.values().next().value)
1707
+ body = moved
1708
+ res = await send(shaped(body))
1709
+ if (res.status === 400) text = await res.text()
1710
+ }
1711
+ const again = res.status === 400 && !endsAfterTools.has(model) && AFTER_TOOLS_REFUSED.test(text) ? replyAfterTools(body) : null
1637
1712
  if (again) {
1638
1713
  endsAfterTools.add(model)
1639
1714
  res = await send(again)
1640
- } else {
1715
+ } else if (res.status === 400) {
1641
1716
  res = new Response(text, { status: res.status, statusText: res.statusText, headers: res.headers })
1642
1717
  }
1643
1718
  }
@@ -1785,4 +1860,4 @@ export const WorkBuddyAuthPlugin = makePlugin(SITES.workbuddy)
1785
1860
  export const WorkBuddyAIAuthPlugin = makePlugin(SITES["workbuddy-ai"])
1786
1861
 
1787
1862
  // for tests
1788
- 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 }
1863
+ 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.28",
3
+ "version": "0.1.30",
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",