opencode-copilot-account-switcher 0.14.6 → 0.14.7

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.
@@ -21,7 +21,7 @@ function pickQrTerminal(value) {
21
21
  return pickFirstNonEmptyString(value?.qrTerminal);
22
22
  }
23
23
  function pickQrUrl(value) {
24
- return pickFirstNonEmptyString(value?.qrDataUrl, value?.qrUrl);
24
+ return pickFirstNonEmptyString(value?.qrDataUrl);
25
25
  }
26
26
  function isTimeoutWaitResult(value) {
27
27
  return Boolean(value && typeof value === "object" && "status" in value && String(value.status) === "timeout");
@@ -33,6 +33,12 @@ async function rollbackBinding(action, previousOperatorBinding, persistOperatorR
33
33
  }
34
34
  await clearOperatorBinding().catch(() => { });
35
35
  }
36
+ function isSameOperatorBinding(left, right) {
37
+ if (!left || !right) {
38
+ return false;
39
+ }
40
+ return left.wechatAccountId === right.wechatAccountId && left.userId === right.userId && left.boundAt === right.boundAt;
41
+ }
36
42
  async function renderQrTerminalDefault(input) {
37
43
  return await new Promise((resolve) => {
38
44
  qrcodeTerminal.generate(input.value, { small: true }, (output) => {
@@ -81,49 +87,44 @@ export async function runWechatBindFlow(input) {
81
87
  if (waited && typeof waited === "object" && "connected" in waited && waited.connected === false) {
82
88
  throw new Error("qr login did not complete");
83
89
  }
84
- const accountId = pickFirstNonEmptyString(waited?.accountId, helpers.latestAccountState?.accountId, (await helpers.accountHelpers.listAccountIds()).at(-1));
90
+ const accountId = pickFirstNonEmptyString(waited?.accountId);
85
91
  if (!accountId) {
86
92
  throw new Error("missing accountId after qr login");
87
93
  }
88
94
  const boundAt = now();
89
- const userIdFromWait = pickFirstNonEmptyString(waited?.userId);
90
95
  const previousOperatorBinding = input.action === "wechat-rebind" ? await loadOperatorBinding() : undefined;
96
+ const userIdFromWait = pickFirstNonEmptyString(waited?.userId);
91
97
  let menuAccount;
92
98
  let boundUserId = "";
93
99
  let shouldRollbackBinding = false;
100
+ let attemptedOperatorBinding;
94
101
  try {
95
- const menuAccountState = accountId
96
- ? {
97
- ...(helpers.latestAccountState ?? {
98
- accountId,
99
- token: "",
100
- baseUrl: "https://ilinkai.weixin.qq.com",
101
- }),
102
- accountId,
103
- ...(userIdFromWait ? { userId: userIdFromWait } : {}),
104
- boundAt,
105
- }
106
- : helpers.latestAccountState;
102
+ const menuAccountState = {
103
+ accountId,
104
+ token: "",
105
+ baseUrl: "https://ilinkai.weixin.qq.com",
106
+ };
107
107
  menuAccount = await buildOpenClawMenuAccount({
108
108
  latestAccountState: menuAccountState,
109
109
  accountHelpers: helpers.accountHelpers,
110
110
  });
111
- const userId = pickFirstNonEmptyString(userIdFromWait, menuAccount?.userId);
111
+ const userId = pickFirstNonEmptyString(menuAccount?.userId, userIdFromWait);
112
112
  if (!userId) {
113
113
  throw new Error("missing userId after qr login");
114
114
  }
115
115
  boundUserId = userId;
116
- const operatorBinding = {
116
+ attemptedOperatorBinding = {
117
117
  wechatAccountId: accountId,
118
118
  userId,
119
119
  boundAt,
120
120
  };
121
- shouldRollbackBinding = true;
122
121
  if (input.action === "wechat-rebind") {
123
- await persistOperatorRebinding(operatorBinding);
122
+ shouldRollbackBinding = true;
123
+ await persistOperatorRebinding(attemptedOperatorBinding);
124
124
  }
125
125
  else {
126
- await persistOperatorBinding(operatorBinding);
126
+ shouldRollbackBinding = true;
127
+ await persistOperatorBinding(attemptedOperatorBinding);
127
128
  }
128
129
  const settings = await input.readCommonSettings();
129
130
  const notifications = settings.wechat?.notifications ?? {
@@ -148,7 +149,15 @@ export async function runWechatBindFlow(input) {
148
149
  }
149
150
  catch (error) {
150
151
  if (shouldRollbackBinding) {
151
- await rollbackBinding(input.action, previousOperatorBinding, persistOperatorRebinding, clearOperatorBinding);
152
+ if (input.action === "wechat-bind") {
153
+ const currentOperatorBinding = await loadOperatorBinding().catch(() => undefined);
154
+ if (isSameOperatorBinding(currentOperatorBinding, attemptedOperatorBinding)) {
155
+ await rollbackBinding(input.action, previousOperatorBinding, persistOperatorRebinding, clearOperatorBinding);
156
+ }
157
+ }
158
+ else {
159
+ await rollbackBinding(input.action, previousOperatorBinding, persistOperatorRebinding, clearOperatorBinding);
160
+ }
152
161
  }
153
162
  throw error;
154
163
  }
@@ -15,33 +15,11 @@ function getAccountJiti() {
15
15
  function asObject(value) {
16
16
  return value && typeof value === "object" ? value : {};
17
17
  }
18
- function toBoolean(value) {
19
- if (typeof value === "boolean") {
20
- return value;
21
- }
22
- if (value === 1) {
23
- return true;
24
- }
25
- if (value === 0) {
26
- return false;
27
- }
28
- return undefined;
29
- }
30
- function deriveEnabled(resolved, stored) {
31
- const explicitEnabled = toBoolean(resolved.enabled) ?? toBoolean(resolved.isEnabled) ?? toBoolean(stored.enabled) ?? toBoolean(stored.isEnabled);
32
- if (explicitEnabled !== undefined) {
33
- return explicitEnabled;
34
- }
35
- if (resolved.disabled === true || stored.disabled === true) {
36
- return false;
37
- }
38
- return true;
18
+ function deriveEnabled(resolved) {
19
+ return resolved.enabled === true;
39
20
  }
40
- function isConfigured(resolved, stored) {
41
- if (typeof resolved.configured === "boolean") {
42
- return resolved.configured;
43
- }
44
- return typeof stored.token === "string" && stored.token.trim().length > 0;
21
+ function isConfigured(resolved) {
22
+ return resolved.configured === true;
45
23
  }
46
24
  function toAccountId(input) {
47
25
  return typeof input === "string" ? input : input.accountId;
@@ -52,8 +30,8 @@ export function createOpenClawAccountHelpers(input) {
52
30
  const stored = asObject(await input.loadAccount(accountId));
53
31
  return {
54
32
  accountId,
55
- enabled: deriveEnabled(resolved, stored),
56
- configured: isConfigured(resolved, stored),
33
+ enabled: deriveEnabled(resolved),
34
+ configured: isConfigured(resolved),
57
35
  name: typeof resolved.name === "string" ? resolved.name : undefined,
58
36
  userId: typeof stored.userId === "string" ? stored.userId : undefined,
59
37
  };
@@ -81,6 +59,13 @@ export async function loadOpenClawAccountHelpers(options = {}) {
81
59
  return createOpenClawAccountHelpers({
82
60
  listAccountIds: () => accountsModule.listIndexedWeixinAccountIds(),
83
61
  loadAccount: (accountId) => accountsModule.loadWeixinAccount(accountId),
84
- resolveAccount: (accountId) => ({ accountId }),
62
+ resolveAccount: async (accountId) => {
63
+ const stored = asObject(await accountsModule.loadWeixinAccount(accountId));
64
+ return {
65
+ accountId,
66
+ enabled: stored.enabled === false ? false : true,
67
+ configured: typeof stored.token === "string" && stored.token.trim().length > 0,
68
+ };
69
+ },
85
70
  });
86
71
  }
@@ -394,7 +394,7 @@ async function runQrLoginDefault(input) {
394
394
  verbose: false,
395
395
  }));
396
396
  const qrTerminal = pickFirstString(startResult, ["qrTerminal"]);
397
- const qrUrl = pickFirstString(startResult, ["qrDataUrl", "qrUrl"]);
397
+ const qrUrl = pickFirstString(startResult, ["qrDataUrl"]);
398
398
  const sessionKey = pickFirstString(startResult, ["sessionKey"]);
399
399
  const qrStartMessage = pickFirstString(startResult, ["message", "detail", "reason"]);
400
400
  if (!sessionKey) {
@@ -695,23 +695,13 @@ function normalizeQrLoginResult(result) {
695
695
  return null;
696
696
  }
697
697
  if (candidate.status === "success") {
698
- if (typeof candidate.connected === "boolean" && candidate.connected !== true) {
699
- return {
700
- status: "timeout",
701
- qrPrinted: candidate.qrPrinted === true,
702
- qrUrl: typeof candidate.qrDataUrl === "string" && candidate.qrDataUrl.trim().length > 0
703
- ? candidate.qrDataUrl
704
- : typeof candidate.qrUrl === "string"
705
- ? candidate.qrUrl
706
- : undefined,
707
- };
698
+ if (candidate.connected !== true) {
699
+ return null;
708
700
  }
709
701
  const hasPrintedQr = candidate.qrPrinted === true;
710
702
  const qrUrl = typeof candidate.qrDataUrl === "string" && candidate.qrDataUrl.trim().length > 0
711
703
  ? candidate.qrDataUrl
712
- : typeof candidate.qrUrl === "string" && candidate.qrUrl.trim().length > 0
713
- ? candidate.qrUrl
714
- : undefined;
704
+ : undefined;
715
705
  const hasQrUrl = Boolean(qrUrl);
716
706
  if (!hasPrintedQr && !hasQrUrl) {
717
707
  return null;
@@ -719,9 +709,7 @@ function normalizeQrLoginResult(result) {
719
709
  }
720
710
  const normalizedQrUrl = typeof candidate.qrDataUrl === "string" && candidate.qrDataUrl.trim().length > 0
721
711
  ? candidate.qrDataUrl
722
- : typeof candidate.qrUrl === "string"
723
- ? candidate.qrUrl
724
- : undefined;
712
+ : undefined;
725
713
  return {
726
714
  status: candidate.status,
727
715
  connected: candidate.connected === true ? true : undefined,
@@ -34,12 +34,6 @@ export async function loadOpenClawQrGateway(payloads, options = {}) {
34
34
  pluginId: resolvedPluginId,
35
35
  };
36
36
  }
37
- if (hasQrLoginMethods(payloadPlugin)) {
38
- return {
39
- gateway: createOpenClawQrGateway(payloadPlugin),
40
- pluginId: resolvedPluginId,
41
- };
42
- }
43
37
  }
44
38
  throw new Error("registerChannel did not expose weixin gateway loginWithQrStart/loginWithQrWait");
45
39
  }
@@ -11,7 +11,7 @@ export declare function createOpenClawSyncBufHelper(input: {
11
11
  export declare function loadOpenClawSyncBufHelper(options?: {
12
12
  syncBufModulePath?: string;
13
13
  }): Promise<{
14
- persistGetUpdatesBuf?: PublicWeixinPersistGetUpdatesBuf;
14
+ persistGetUpdatesBuf: PublicWeixinPersistGetUpdatesBuf;
15
15
  }>;
16
16
  export declare function loadLatestWeixinAccountState(options?: {
17
17
  stateDirModulePath?: string;
@@ -19,6 +19,9 @@ export function createOpenClawSyncBufHelper(input) {
19
19
  return {
20
20
  async persistGetUpdatesBuf({ accountId, getUpdatesBuf }) {
21
21
  const filePath = input.getSyncBufFilePath(accountId);
22
+ if (typeof filePath !== "string" || filePath.trim().length === 0) {
23
+ throw new Error("[wechat-compat] sync-buf helper returned invalid file path");
24
+ }
22
25
  input.saveGetUpdatesBuf(filePath, getUpdatesBuf);
23
26
  },
24
27
  };
@@ -28,7 +31,7 @@ export async function loadOpenClawSyncBufHelper(options = {}) {
28
31
  const syncBufModulePath = require.resolve(options.syncBufModulePath ?? OPENCLAW_SYNC_BUF_MODULE);
29
32
  const syncBufModule = getSyncBufJiti()(syncBufModulePath);
30
33
  if (typeof syncBufModule.getSyncBufFilePath !== "function" || typeof syncBufModule.saveGetUpdatesBuf !== "function") {
31
- return {};
34
+ throw new Error("[wechat-compat] sync-buf source helper unavailable");
32
35
  }
33
36
  return createOpenClawSyncBufHelper({
34
37
  getSyncBufFilePath: syncBufModule.getSyncBufFilePath,
@@ -13,20 +13,6 @@ function toBoolean(value) {
13
13
  if (typeof value === "boolean") {
14
14
  return value;
15
15
  }
16
- if (value === 1) {
17
- return true;
18
- }
19
- if (value === 0) {
20
- return false;
21
- }
22
- return undefined;
23
- }
24
- function firstNumber(...values) {
25
- for (const value of values) {
26
- if (typeof value === "number" && Number.isFinite(value)) {
27
- return value;
28
- }
29
- }
30
16
  return undefined;
31
17
  }
32
18
  async function tryDescribeAccount(accountHelpers, accountId) {
@@ -55,13 +41,17 @@ export async function buildOpenClawMenuAccount(input) {
55
41
  }
56
42
  const resolvedRaw = asObject(await input.accountHelpers.resolveAccount(accountId));
57
43
  const describedRaw = asObject(await tryDescribeAccount(input.accountHelpers, accountId));
58
- const enabled = toBoolean(resolvedRaw.enabled) ?? toBoolean(resolvedRaw.isEnabled) ?? toBoolean(describedRaw.enabled) ?? toBoolean(describedRaw.isEnabled) ?? false;
59
- const configured = toBoolean(describedRaw.configured) ?? toBoolean(describedRaw.isConfigured) ?? toBoolean(resolvedRaw.configured) ?? toBoolean(resolvedRaw.isConfigured) ?? false;
60
- const userId = firstNonEmptyString(input.latestAccountState?.userId, resolvedRaw.userId, resolvedRaw.user_id, describedRaw.userId, describedRaw.user_id);
61
- const boundAt = firstNumber(input.latestAccountState?.boundAt, input.latestAccountState?.savedAt, resolvedRaw.boundAt, resolvedRaw.savedAt, describedRaw.boundAt, describedRaw.savedAt);
44
+ const enabled = toBoolean(resolvedRaw.enabled) ?? toBoolean(describedRaw.enabled) ?? false;
45
+ const configured = toBoolean(describedRaw.configured) ?? toBoolean(resolvedRaw.configured) ?? false;
46
+ const userId = firstNonEmptyString(input.latestAccountState?.userId, resolvedRaw.userId, describedRaw.userId);
47
+ const boundAt = [
48
+ input.latestAccountState?.boundAt,
49
+ resolvedRaw.boundAt,
50
+ describedRaw.boundAt,
51
+ ].find((value) => typeof value === "number" && Number.isFinite(value));
62
52
  return {
63
53
  accountId,
64
- name: firstNonEmptyString(resolvedRaw.name, resolvedRaw.displayName, describedRaw.name, describedRaw.displayName),
54
+ name: firstNonEmptyString(resolvedRaw.name, describedRaw.name),
65
55
  enabled,
66
56
  configured,
67
57
  userId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-copilot-account-switcher",
3
- "version": "0.14.6",
3
+ "version": "0.14.7",
4
4
  "description": "GitHub Copilot account switcher plugin for OpenCode",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",