@magpie-community/opencode-workbuddy-auth 0.2.2 → 0.2.3
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 +27 -15
- package/index.mjs +118 -3
- package/package.json +16 -4
package/README.md
CHANGED
|
@@ -174,21 +174,33 @@ default one, as the app does.
|
|
|
174
174
|
|
|
175
175
|
WorkBuddy refuses some agents' identity lines as an "unapproved channel"
|
|
176
176
|
(11128), so those lines are reworded before a chat goes out. Since 0.1.27
|
|
177
|
-
(#279) that includes DeepSeek Harness's opening line.
|
|
178
|
-
not reworded yet is
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
177
|
+
(#279) that includes DeepSeek Harness's opening line. Since 0.2.3 a refusal
|
|
178
|
+
of words not reworded yet is answered first by saying the same line with one
|
|
179
|
+
more comma — a vendor matches such a line whole and exactly, so the comma
|
|
180
|
+
passes it while the line still names the harness (measured 2026-10-30; a word
|
|
181
|
+
added after the product name did not pass, the comma did). The comma is set in
|
|
182
|
+
the system and assistant messages only — the two WorkBuddy reads — so the
|
|
183
|
+
user's own words and tools' results go out as they were sent. A system prompt
|
|
184
|
+
whose comma passed is remembered while the plugin is loaded, as a moved one
|
|
185
|
+
is, and its next chats go with the comma already in before the first send:
|
|
186
|
+
otherwise every turn, and every step of a tool loop, would pay the refusal
|
|
187
|
+
once. Only a chat refused still is sent once more, since 0.1.29, with the
|
|
188
|
+
system prompt moved into the first user message (inside `<instructions>` tags)
|
|
189
|
+
and WorkBuddy's default system prompt in its place: WorkBuddy checks only the
|
|
190
|
+
system and assistant messages, and the line it matched is then in neither. A system message later
|
|
191
|
+
in the chat becomes a user message where it stands. The plugin remembers each
|
|
192
|
+
refused system prompt while it is loaded, so that agent's next chats go moved
|
|
193
|
+
at once. The move can't reach an assistant message, so words the model quotes
|
|
194
|
+
back into one keep that chat refused until they are in the list reworded in
|
|
195
|
+
every message (since 0.1.30, the "Web GUI whose URL the runtime context names"
|
|
196
|
+
line, #291); otherwise only a new chat clears it. A refusal that stands after
|
|
197
|
+
the move ends in a hint that names no one client (since 0.1.28): WorkBuddy
|
|
198
|
+
refused words in this client's prompt; use it from Hermes, OpenCode or Pi, or
|
|
199
|
+
add another provider to the group. Since 0.2.3 that hint is preceded by the
|
|
200
|
+
address of [REFUSED.md](REFUSED.md), which says what the check reads, how a
|
|
201
|
+
line is reworded to pass it, and how to turn such a line off in an agent that
|
|
202
|
+
has a switch for it — so the error carries its own fix, for the person who
|
|
203
|
+
reads it or the agent they paste it to. Neither refusal rests the account.
|
|
192
204
|
|
|
193
205
|
The loader also translates OpenAI-compatible request spellings WorkBuddy
|
|
194
206
|
doesn't take: `max_completion_tokens` becomes `max_tokens`, `developer`
|
package/index.mjs
CHANGED
|
@@ -1008,6 +1008,79 @@ function systemAsUserOn(b) {
|
|
|
1008
1008
|
return true
|
|
1009
1009
|
}
|
|
1010
1010
|
|
|
1011
|
+
// OPENERS are the shapes in which an agent's opening says which harness it
|
|
1012
|
+
// runs in, as a vendor may have fingerprinted one. A vendor matches a refused
|
|
1013
|
+
// line whole and exactly — one extra space passes it, as FLAGGED's note above
|
|
1014
|
+
// has it — so the same line with one more comma passes while still saying
|
|
1015
|
+
// what it said. Measured 2026-10-30 against copilot.tencent.com, one account,
|
|
1016
|
+
// streamed, deepseek-v4.1-flash at max: DeepSeek Harness's opener answered
|
|
1017
|
+
// 400 code 11128 as written, and 200 with the comma, the harness's name still
|
|
1018
|
+
// in it; a suffix in its place (…Harness Desktop., …Harness CLI.) changed
|
|
1019
|
+
// nothing, so it is the line's shape the vendor knows, not the product alone.
|
|
1020
|
+
const OPENERS = [
|
|
1021
|
+
[/(\bYou are (?:an?|the)\s[^\n.,]{0,60}?\b(?:agent|assistant))\s+(powered by|running on|running in|built on|based on)\b/gi, "$1, $2"],
|
|
1022
|
+
]
|
|
1023
|
+
|
|
1024
|
+
function opened(text) {
|
|
1025
|
+
for (const [re, to] of OPENERS) text = text.replace(re, to)
|
|
1026
|
+
return text
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// commaPaused is body with OPENERS' comma set in its system and assistant
|
|
1030
|
+
// messages — the two WorkBuddy reads (REFUSED.md). The user's messages and
|
|
1031
|
+
// tools' results are not among them: a comma in a message the vendor never
|
|
1032
|
+
// matches changes what someone said without changing what was refused, and
|
|
1033
|
+
// the user's own words come back altered. It is tried before systemAsUser,
|
|
1034
|
+
// which takes the system prompt out of the slot an agent's instructions are
|
|
1035
|
+
// read in. null when there was nothing to set a comma in.
|
|
1036
|
+
//
|
|
1037
|
+
// The two roles are read off the message's own role: a message with no role,
|
|
1038
|
+
// or one of another kind, is left as it is. roles, when given, collects the
|
|
1039
|
+
// roles the comma went into — the caller uses it to tell a comma that made a
|
|
1040
|
+
// chat pass in its system prompt from one that only set an assistant
|
|
1041
|
+
// message's (commaSystems).
|
|
1042
|
+
const PAUSED_ROLES = new Set(["system", "assistant"])
|
|
1043
|
+
|
|
1044
|
+
function commaPaused(body, roles) {
|
|
1045
|
+
const b = chatOf(body)
|
|
1046
|
+
return commaPausedOn(b, roles) ? JSON.stringify(b) : null
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// commaPausedOn makes commaPaused's change on a chat's object, in place; false
|
|
1050
|
+
// when there was nothing to set a comma in. commaPaused is this on a body, and
|
|
1051
|
+
// the first send of a system prompt whose comma passed is this too
|
|
1052
|
+
// (commaSystems), so the two can never drift apart.
|
|
1053
|
+
function commaPausedOn(b, roles) {
|
|
1054
|
+
const msgs = b?.messages
|
|
1055
|
+
if (!Array.isArray(msgs)) return false
|
|
1056
|
+
let changed = false
|
|
1057
|
+
for (const m of msgs) {
|
|
1058
|
+
if (!PAUSED_ROLES.has(m?.role)) continue
|
|
1059
|
+
let mine = false
|
|
1060
|
+
if (typeof m.content === "string") {
|
|
1061
|
+
const t = opened(m.content)
|
|
1062
|
+
if (t !== m.content) {
|
|
1063
|
+
m.content = t
|
|
1064
|
+
mine = true
|
|
1065
|
+
}
|
|
1066
|
+
} else if (Array.isArray(m.content)) {
|
|
1067
|
+
for (const p of m.content) {
|
|
1068
|
+
if (typeof p?.text !== "string") continue
|
|
1069
|
+
const t = opened(p.text)
|
|
1070
|
+
if (t === p.text) continue
|
|
1071
|
+
p.text = t
|
|
1072
|
+
mine = true
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
// a message whose parts held no opening of their own is not changed, and
|
|
1076
|
+
// is not reported as one that was
|
|
1077
|
+
if (!mine) continue
|
|
1078
|
+
changed = true
|
|
1079
|
+
roles?.add(m.role)
|
|
1080
|
+
}
|
|
1081
|
+
return changed
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1011
1084
|
// normalizeToolChoice converts the object form emitted by OpenAI clients to
|
|
1012
1085
|
// WorkBuddy's string form. It runs after fitted(), so a named choice follows a
|
|
1013
1086
|
// tool name that was made safe for WorkBuddy.
|
|
@@ -3040,6 +3113,13 @@ function makePlugin(site) {
|
|
|
3040
3113
|
// the system prompts this run WorkBuddy refused as an unapproved channel:
|
|
3041
3114
|
// their next chats go with systemAsUser at once, not refused first
|
|
3042
3115
|
const refusedSystems = new Set()
|
|
3116
|
+
// the system prompts this run WorkBuddy refused as written and took with
|
|
3117
|
+
// OPENERS' comma: their next chats go with the comma set before the first
|
|
3118
|
+
// send, as a moved one goes moved. Without it every turn of that agent —
|
|
3119
|
+
// and every step of its tool loop — pays a refused round trip first, which
|
|
3120
|
+
// is latency on each request and a pattern of channel refusals on the
|
|
3121
|
+
// account. Bounded as refusedSystems is.
|
|
3122
|
+
const commaSystems = new Set()
|
|
3043
3123
|
return async ({ client, magpie }) => {
|
|
3044
3124
|
// each account's list as last read, for FREE_ONLY: {at, list}
|
|
3045
3125
|
const lists = new Map()
|
|
@@ -3290,6 +3370,10 @@ function makePlugin(site) {
|
|
|
3290
3370
|
// words at once (see systemAsUser)
|
|
3291
3371
|
const sysKey = native ? "" : systemKeyOf(chat)
|
|
3292
3372
|
if (sysKey && refusedSystems.has(sysKey) && systemAsUserOn(chat)) changed = true
|
|
3373
|
+
// a system prompt whose comma passed once goes with the comma
|
|
3374
|
+
// set before the first send (see commaSystems), the way one
|
|
3375
|
+
// refused before goes moved above
|
|
3376
|
+
if (sysKey && commaSystems.has(sysKey) && commaPausedOn(chat)) changed = true
|
|
3293
3377
|
// shaped is a body as such a model takes it. The first try's
|
|
3294
3378
|
// is made on the chat already parsed, after the move above,
|
|
3295
3379
|
// as shaped would make it of the body written; a resend with
|
|
@@ -3317,7 +3401,27 @@ function makePlugin(site) {
|
|
|
3317
3401
|
else res = new Response(text, { status: res.status, statusText: res.statusText, headers: res.headers })
|
|
3318
3402
|
}
|
|
3319
3403
|
if (res.status === 400 && !native) { let text = await res.text()
|
|
3320
|
-
|
|
3404
|
+
// the vendor may know a shape of the agent's opening that FLAGGED
|
|
3405
|
+
// doesn't: the same words said with one more comma pass it, and
|
|
3406
|
+
// that is tried before the system prompt is moved out of its slot.
|
|
3407
|
+
// The roles the comma went into are kept: one set in the system
|
|
3408
|
+
// prompt is what its next chats go with, one set in an assistant
|
|
3409
|
+
// message alone says nothing about the next chat's.
|
|
3410
|
+
const roles = new Set()
|
|
3411
|
+
const paused = REFUSED.test(text) ? commaPaused(body, roles) : null
|
|
3412
|
+
if (paused) {
|
|
3413
|
+
body = paused
|
|
3414
|
+
res = await send(shaped(body))
|
|
3415
|
+
if (res.status === 400) text = await res.text()
|
|
3416
|
+
// not refused this time: the comma is what passed the
|
|
3417
|
+
// vendor's check, so this system prompt's next chats go
|
|
3418
|
+
// with it set before the first send
|
|
3419
|
+
else if (sysKey && roles.has("system")) {
|
|
3420
|
+
commaSystems.add(sysKey)
|
|
3421
|
+
if (commaSystems.size > 256) commaSystems.delete(commaSystems.values().next().value)
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
const moved = res.status === 400 && REFUSED.test(text) && sysKey && !refusedSystems.has(sysKey) ? systemAsUser(body) : null
|
|
3321
3425
|
if (moved) {
|
|
3322
3426
|
refusedSystems.add(sysKey)
|
|
3323
3427
|
if (refusedSystems.size > 256) refusedSystems.delete(refusedSystems.values().next().value)
|
|
@@ -3446,6 +3550,13 @@ function makePlugin(site) {
|
|
|
3446
3550
|
// apart from WorkBuddy's own words.
|
|
3447
3551
|
const REFUSED_HINT =
|
|
3448
3552
|
"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"
|
|
3553
|
+
|
|
3554
|
+
// REFUSED_DOC is where a refusal's fix is written down, for whoever holds the
|
|
3555
|
+
// error next: the person reading it, or the agent they paste it to. It goes
|
|
3556
|
+
// before the hint, never after, because magpie's Routing page tells the hint
|
|
3557
|
+
// from WorkBuddy's own words by the error ending in it.
|
|
3558
|
+
const REFUSED_DOC = "https://github.com/magpie-community/plugins/blob/main/packages/workbuddy/REFUSED.md"
|
|
3559
|
+
|
|
3449
3560
|
const REFUSED = /unapproved channel|illegal api invocation/i
|
|
3450
3561
|
|
|
3451
3562
|
// BAD_SIGNATURE is WorkBuddy's refusal of a thinking block whose signature it
|
|
@@ -3524,7 +3635,11 @@ async function explained(res) {
|
|
|
3524
3635
|
const hint = isRefused ? REFUSED_HINT : isRestricted ? RESTRICTED_HINT : EXHAUSTED_HINT
|
|
3525
3636
|
const type = isRefused || isRestricted ? "permission_error" : "insufficient_quota"
|
|
3526
3637
|
const errCode = isRefused ? null : isRestricted ? RESTRICTED_CODE : 14018
|
|
3527
|
-
|
|
3638
|
+
// a refusal says where its fix is written down, so whoever holds the error
|
|
3639
|
+
// — the person, or the agent they paste it to — can go and read it. It goes
|
|
3640
|
+
// before the hint, never after: the hint is the message's end.
|
|
3641
|
+
const where = isRefused ? `${REFUSED_DOC} — ` : ""
|
|
3642
|
+
msg = `${msg} — ${where}${hint}`
|
|
3528
3643
|
const headers = new Headers(res.headers)
|
|
3529
3644
|
headers.set("content-type", "application/json")
|
|
3530
3645
|
headers.delete("content-length")
|
|
@@ -3537,4 +3652,4 @@ export const WorkBuddyAuthPlugin = makePlugin(SITES.workbuddy)
|
|
|
3537
3652
|
export const WorkBuddyAIAuthPlugin = makePlugin(SITES["workbuddy-ai"])
|
|
3538
3653
|
|
|
3539
3654
|
// for tests
|
|
3540
|
-
export const _internal = { idFor, replyAfterToolsOn, withDefaultEffort, defaultEffortOf, gzipped, FREE_ONLY, PRICE_TTL, keyAccount, remoteConfig, cycleOf, typeUnions, systemAsUser, systemKey, DEFAULT_SYSTEM, EDGE_READS, fitShape, replyAfterTools, AFTER_TOOLS_REFUSED, enterpriseUsageOf, accountName, notDesktop, textOnly, runsOutOf, packagesOf, endOf, enterpriseHeaders, enterpriseOf, liveModels, browserSignIn, withSystem, asReasoning, normalizeToolChoice, fitTools, fitted, renamed, unflagged, usageOf, desktopHeld, explained, REFUSED_HINT, RESTRICTED_HINT, EXHAUSTED_HINT, freeCredits, creditsOf, priceOf, fresh, SITES, renewing, renewed, SESSION, attend, scopeOf, turnKey, conversationKey, signatureOf, isMessages, withoutUnsigned, withoutSignedThinking, prepared, answeredBefore, nativeOf, modelOf, configModels, freeWindowOf, tokensShort, freeWindowRows, freeWindowLedger, readLedgerTail, pinned, rowTime, rowAccount, ofAccount, windowOffset, windowReset, FREE_WINDOW_MODELS, FREE_WINDOW_SPAN, FREE_WINDOW_GAP, FREE_WINDOW_LEAD, checkinOf, API_MESSAGES: NPM_MESSAGES, API_CHAT: NPM }
|
|
3655
|
+
export const _internal = { idFor, replyAfterToolsOn, withDefaultEffort, defaultEffortOf, gzipped, FREE_ONLY, PRICE_TTL, keyAccount, remoteConfig, cycleOf, typeUnions, systemAsUser, systemKey, DEFAULT_SYSTEM, EDGE_READS, fitShape, replyAfterTools, AFTER_TOOLS_REFUSED, enterpriseUsageOf, accountName, notDesktop, textOnly, runsOutOf, packagesOf, endOf, enterpriseHeaders, enterpriseOf, liveModels, browserSignIn, withSystem, asReasoning, normalizeToolChoice, fitTools, fitted, renamed, unflagged, usageOf, desktopHeld, explained, REFUSED_HINT, RESTRICTED_HINT, EXHAUSTED_HINT, REFUSED_DOC, commaPaused, commaPausedOn, opened, freeCredits, creditsOf, priceOf, fresh, SITES, renewing, renewed, SESSION, attend, scopeOf, turnKey, conversationKey, signatureOf, isMessages, withoutUnsigned, withoutSignedThinking, prepared, answeredBefore, nativeOf, modelOf, configModels, freeWindowOf, tokensShort, freeWindowRows, freeWindowLedger, readLedgerTail, pinned, rowTime, rowAccount, ofAccount, windowOffset, windowReset, FREE_WINDOW_MODELS, FREE_WINDOW_SPAN, FREE_WINDOW_GAP, FREE_WINDOW_LEAD, checkinOf, API_MESSAGES: NPM_MESSAGES, API_CHAT: NPM }
|
package/package.json
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magpie-community/opencode-workbuddy-auth",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
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",
|
|
7
7
|
"exports": "./index.mjs",
|
|
8
|
-
"files": [
|
|
9
|
-
|
|
8
|
+
"files": [
|
|
9
|
+
"index.mjs",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"keywords": [
|
|
13
|
+
"opencode",
|
|
14
|
+
"opencode-plugin",
|
|
15
|
+
"workbuddy",
|
|
16
|
+
"codebuddy"
|
|
17
|
+
],
|
|
10
18
|
"license": "MIT",
|
|
11
|
-
"repository": {
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/magpie-community/plugins.git",
|
|
22
|
+
"directory": "packages/workbuddy"
|
|
23
|
+
}
|
|
12
24
|
}
|