opencode-copilot-account-switcher 0.14.0 → 0.14.2

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.
@@ -1,5 +1,5 @@
1
1
  import type { CommonSettingsStore } from "./common-settings-store.js";
2
- export type CommonSettingsActionType = "toggle-loop-safety" | "toggle-loop-safety-provider-scope" | "toggle-experimental-slash-commands" | "toggle-network-retry" | "toggle-wechat-notifications" | "toggle-wechat-question-notify" | "toggle-wechat-permission-notify" | "toggle-wechat-session-error-notify";
2
+ export type CommonSettingsActionType = "toggle-loop-safety" | "toggle-loop-safety-provider-scope" | "toggle-experimental-slash-commands" | "toggle-network-retry" | "wechat-bind" | "wechat-rebind" | "wechat-unbind" | "toggle-wechat-notifications" | "toggle-wechat-question-notify" | "toggle-wechat-permission-notify" | "toggle-wechat-session-error-notify";
3
3
  type WriteMeta = {
4
4
  reason?: string;
5
5
  source?: string;
@@ -1,5 +1,20 @@
1
1
  export async function applyCommonSettingsAction(input) {
2
2
  const settings = await input.readSettings();
3
+ const existingNotifications = settings.wechat?.notifications;
4
+ const notifications = {
5
+ enabled: existingNotifications?.enabled !== false,
6
+ question: existingNotifications?.question !== false,
7
+ permission: existingNotifications?.permission !== false,
8
+ sessionError: existingNotifications?.sessionError !== false,
9
+ };
10
+ if (!settings.wechat) {
11
+ settings.wechat = {
12
+ notifications,
13
+ };
14
+ }
15
+ else if (!settings.wechat.notifications) {
16
+ settings.wechat.notifications = notifications;
17
+ }
3
18
  if (input.action.type === "toggle-loop-safety") {
4
19
  settings.loopSafetyEnabled = settings.loopSafetyEnabled !== true;
5
20
  await input.writeSettings(settings, {
@@ -39,7 +54,7 @@ export async function applyCommonSettingsAction(input) {
39
54
  return true;
40
55
  }
41
56
  if (input.action.type === "toggle-wechat-notifications") {
42
- settings.wechatNotificationsEnabled = settings.wechatNotificationsEnabled !== true;
57
+ settings.wechat.notifications.enabled = settings.wechat.notifications.enabled !== true;
43
58
  await input.writeSettings(settings, {
44
59
  reason: "toggle-wechat-notifications",
45
60
  source: "applyCommonSettingsAction",
@@ -48,7 +63,7 @@ export async function applyCommonSettingsAction(input) {
48
63
  return true;
49
64
  }
50
65
  if (input.action.type === "toggle-wechat-question-notify") {
51
- settings.wechatQuestionNotifyEnabled = settings.wechatQuestionNotifyEnabled !== true;
66
+ settings.wechat.notifications.question = settings.wechat.notifications.question !== true;
52
67
  await input.writeSettings(settings, {
53
68
  reason: "toggle-wechat-question-notify",
54
69
  source: "applyCommonSettingsAction",
@@ -57,7 +72,7 @@ export async function applyCommonSettingsAction(input) {
57
72
  return true;
58
73
  }
59
74
  if (input.action.type === "toggle-wechat-permission-notify") {
60
- settings.wechatPermissionNotifyEnabled = settings.wechatPermissionNotifyEnabled !== true;
75
+ settings.wechat.notifications.permission = settings.wechat.notifications.permission !== true;
61
76
  await input.writeSettings(settings, {
62
77
  reason: "toggle-wechat-permission-notify",
63
78
  source: "applyCommonSettingsAction",
@@ -66,7 +81,7 @@ export async function applyCommonSettingsAction(input) {
66
81
  return true;
67
82
  }
68
83
  if (input.action.type === "toggle-wechat-session-error-notify") {
69
- settings.wechatSessionErrorNotifyEnabled = settings.wechatSessionErrorNotifyEnabled !== true;
84
+ settings.wechat.notifications.sessionError = settings.wechat.notifications.sessionError !== true;
70
85
  await input.writeSettings(settings, {
71
86
  reason: "toggle-wechat-session-error-notify",
72
87
  source: "applyCommonSettingsAction",
@@ -74,5 +89,8 @@ export async function applyCommonSettingsAction(input) {
74
89
  });
75
90
  return true;
76
91
  }
92
+ if (input.action.type === "wechat-bind" || input.action.type === "wechat-rebind" || input.action.type === "wechat-unbind") {
93
+ return true;
94
+ }
77
95
  return false;
78
96
  }
@@ -4,11 +4,32 @@ export type CommonSettingsStore = {
4
4
  networkRetryEnabled?: boolean;
5
5
  experimentalSlashCommandsEnabled?: boolean;
6
6
  experimentalStatusSlashCommandEnabled?: boolean;
7
+ wechat?: WechatMenuSettings;
7
8
  wechatNotificationsEnabled?: boolean;
8
9
  wechatQuestionNotifyEnabled?: boolean;
9
10
  wechatPermissionNotifyEnabled?: boolean;
10
11
  wechatSessionErrorNotifyEnabled?: boolean;
11
12
  };
13
+ export type WechatBinding = {
14
+ accountId: string;
15
+ userId?: string;
16
+ name?: string;
17
+ enabled?: boolean;
18
+ configured?: boolean;
19
+ boundAt?: number;
20
+ };
21
+ export type WechatMenuSettings = {
22
+ primaryBinding?: WechatBinding;
23
+ notifications: {
24
+ enabled: boolean;
25
+ question: boolean;
26
+ permission: boolean;
27
+ sessionError: boolean;
28
+ };
29
+ future?: {
30
+ accounts?: WechatBinding[];
31
+ };
32
+ };
12
33
  export declare function parseCommonSettingsStore(raw: string): CommonSettingsStore;
13
34
  export declare function commonSettingsPath(): string;
14
35
  export declare function readCommonSettingsStore(options?: {
@@ -3,9 +3,68 @@ import { promises as fs } from "node:fs";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { commonSettingsPath as defaultCommonSettingsPath, legacyCopilotStorePath } from "./store-paths.js";
5
5
  import { parseStore } from "./store.js";
6
+ function normalizeWechatBinding(input) {
7
+ if (!input || typeof input !== "object")
8
+ return undefined;
9
+ const value = input;
10
+ if (typeof value.accountId !== "string" || value.accountId.length === 0)
11
+ return undefined;
12
+ const binding = { accountId: value.accountId };
13
+ if (typeof value.userId === "string")
14
+ binding.userId = value.userId;
15
+ if (typeof value.name === "string")
16
+ binding.name = value.name;
17
+ if (typeof value.enabled === "boolean")
18
+ binding.enabled = value.enabled;
19
+ if (typeof value.configured === "boolean")
20
+ binding.configured = value.configured;
21
+ if (typeof value.boundAt === "number" && Number.isFinite(value.boundAt))
22
+ binding.boundAt = value.boundAt;
23
+ return binding;
24
+ }
25
+ function normalizeWechatSettings(source) {
26
+ const wechatValue = source.wechat && typeof source.wechat === "object"
27
+ ? source.wechat
28
+ : undefined;
29
+ const notificationsValue = wechatValue?.notifications && typeof wechatValue.notifications === "object"
30
+ ? wechatValue.notifications
31
+ : undefined;
32
+ const futureValue = wechatValue?.future && typeof wechatValue.future === "object"
33
+ ? wechatValue.future
34
+ : undefined;
35
+ const enabled = typeof notificationsValue?.enabled === "boolean"
36
+ ? notificationsValue.enabled
37
+ : source.wechatNotificationsEnabled !== false;
38
+ const question = typeof notificationsValue?.question === "boolean"
39
+ ? notificationsValue.question
40
+ : source.wechatQuestionNotifyEnabled !== false;
41
+ const permission = typeof notificationsValue?.permission === "boolean"
42
+ ? notificationsValue.permission
43
+ : source.wechatPermissionNotifyEnabled !== false;
44
+ const sessionError = typeof notificationsValue?.sessionError === "boolean"
45
+ ? notificationsValue.sessionError
46
+ : source.wechatSessionErrorNotifyEnabled !== false;
47
+ const primaryBinding = normalizeWechatBinding(wechatValue?.primaryBinding);
48
+ const accounts = Array.isArray(futureValue?.accounts)
49
+ ? futureValue.accounts
50
+ .map((account) => normalizeWechatBinding(account))
51
+ .filter((account) => Boolean(account))
52
+ : undefined;
53
+ return {
54
+ ...(primaryBinding ? { primaryBinding } : {}),
55
+ notifications: {
56
+ enabled,
57
+ question,
58
+ permission,
59
+ sessionError,
60
+ },
61
+ ...(accounts ? { future: { accounts } } : {}),
62
+ };
63
+ }
6
64
  function normalizeCommonSettingsStore(input) {
7
65
  const source = input ?? {};
8
66
  const legacySlashCommandsEnabled = source.experimentalStatusSlashCommandEnabled;
67
+ const wechat = normalizeWechatSettings(source);
9
68
  return {
10
69
  ...(source.loopSafetyEnabled !== false ? { loopSafetyEnabled: true } : { loopSafetyEnabled: false }),
11
70
  loopSafetyProviderScope: source.loopSafetyProviderScope === "all-models" ? "all-models" : "copilot-only",
@@ -15,12 +74,7 @@ function normalizeCommonSettingsStore(input) {
15
74
  : legacySlashCommandsEnabled === false
16
75
  ? false
17
76
  : true,
18
- ...(source.wechatNotificationsEnabled === false ? { wechatNotificationsEnabled: false } : { wechatNotificationsEnabled: true }),
19
- ...(source.wechatQuestionNotifyEnabled === false ? { wechatQuestionNotifyEnabled: false } : { wechatQuestionNotifyEnabled: true }),
20
- ...(source.wechatPermissionNotifyEnabled === false ? { wechatPermissionNotifyEnabled: false } : { wechatPermissionNotifyEnabled: true }),
21
- ...(source.wechatSessionErrorNotifyEnabled === false
22
- ? { wechatSessionErrorNotifyEnabled: false }
23
- : { wechatSessionErrorNotifyEnabled: true }),
77
+ wechat,
24
78
  };
25
79
  }
26
80
  function parsePartialCommonSettingsStore(raw) {
@@ -41,6 +95,9 @@ function parsePartialCommonSettingsStore(raw) {
41
95
  if (parsed.experimentalStatusSlashCommandEnabled === true || parsed.experimentalStatusSlashCommandEnabled === false) {
42
96
  partial.experimentalStatusSlashCommandEnabled = parsed.experimentalStatusSlashCommandEnabled;
43
97
  }
98
+ if (parsed.wechat && typeof parsed.wechat === "object") {
99
+ partial.wechat = parsed.wechat;
100
+ }
44
101
  if (parsed.wechatNotificationsEnabled === true || parsed.wechatNotificationsEnabled === false) {
45
102
  partial.wechatNotificationsEnabled = parsed.wechatNotificationsEnabled;
46
103
  }
@@ -73,6 +130,7 @@ function mergeCommonSettings(current, legacy) {
73
130
  networkRetryEnabled: current.networkRetryEnabled ?? legacy.networkRetryEnabled,
74
131
  experimentalSlashCommandsEnabled: current.experimentalSlashCommandsEnabled ?? legacy.experimentalSlashCommandsEnabled,
75
132
  experimentalStatusSlashCommandEnabled: current.experimentalStatusSlashCommandEnabled ?? legacy.experimentalStatusSlashCommandEnabled,
133
+ wechat: current.wechat,
76
134
  wechatNotificationsEnabled: current.wechatNotificationsEnabled ?? legacy.wechatNotificationsEnabled,
77
135
  wechatQuestionNotifyEnabled: current.wechatQuestionNotifyEnabled ?? legacy.wechatQuestionNotifyEnabled,
78
136
  wechatPermissionNotifyEnabled: current.wechatPermissionNotifyEnabled ?? legacy.wechatPermissionNotifyEnabled,
@@ -138,10 +196,7 @@ export async function writeCommonSettingsStore(store, options) {
138
196
  loopSafetyProviderScope: normalized.loopSafetyProviderScope,
139
197
  networkRetryEnabled: normalized.networkRetryEnabled,
140
198
  experimentalSlashCommandsEnabled: normalized.experimentalSlashCommandsEnabled,
141
- wechatNotificationsEnabled: normalized.wechatNotificationsEnabled,
142
- wechatQuestionNotifyEnabled: normalized.wechatQuestionNotifyEnabled,
143
- wechatPermissionNotifyEnabled: normalized.wechatPermissionNotifyEnabled,
144
- wechatSessionErrorNotifyEnabled: normalized.wechatSessionErrorNotifyEnabled,
199
+ wechat: normalized.wechat,
145
200
  };
146
201
  await fs.mkdir(path.dirname(file), { recursive: true });
147
202
  await fs.writeFile(file, JSON.stringify(persisted, null, 2), { mode: 0o600 });
package/dist/plugin.js CHANGED
@@ -33,6 +33,10 @@ function toSharedRuntimeAction(action) {
33
33
  return { type: "provider", name: "toggle-experimental-slash-commands" };
34
34
  if (action.type === "toggle-network-retry")
35
35
  return { type: "provider", name: "toggle-network-retry" };
36
+ if (action.type === "wechat-bind")
37
+ return { type: "provider", name: "wechat-bind" };
38
+ if (action.type === "wechat-rebind")
39
+ return { type: "provider", name: "wechat-rebind" };
36
40
  return undefined;
37
41
  }
38
42
  export async function configureDefaultAccountGroup(store, selectors) {
@@ -7,6 +7,7 @@ import { recoverInvalidCodexAccount } from "../codex-invalid-account.js";
7
7
  import { readAuth } from "../store.js";
8
8
  import { readCommonSettingsStore, writeCommonSettingsStore, } from "../common-settings-store.js";
9
9
  import { applyCommonSettingsAction } from "../common-settings-actions.js";
10
+ import { runWechatBindFlow } from "../wechat/bind-flow.js";
10
11
  function pickName(input) {
11
12
  const accountId = input.accountId?.trim();
12
13
  if (accountId)
@@ -382,8 +383,13 @@ export function createCodexMenuAdapter(inputDeps) {
382
383
  });
383
384
  return false;
384
385
  }
385
- if (action.name === "wechat-bind") {
386
- return true;
386
+ if (action.name === "wechat-bind" || action.name === "wechat-rebind") {
387
+ await runWechatBindFlow({
388
+ action: action.name,
389
+ readCommonSettings,
390
+ writeCommonSettings,
391
+ });
392
+ return false;
387
393
  }
388
394
  return false;
389
395
  },
@@ -5,6 +5,7 @@ import { getGitHubToken, normalizeDomain } from "../copilot-api-helpers.js";
5
5
  import { listAssignableAccountsForModel, listKnownCopilotModels, rewriteModelAccountAssignments, } from "../model-account-map.js";
6
6
  import { applyMenuAction, persistAccountSwitch } from "../plugin-actions.js";
7
7
  import { readCommonSettingsStore, writeCommonSettingsStore, } from "../common-settings-store.js";
8
+ import { runWechatBindFlow } from "../wechat/bind-flow.js";
8
9
  import { select, selectMany } from "../ui/select.js";
9
10
  import { authPath, readAuth, readStore } from "../store.js";
10
11
  const CLIENT_ID = "Ov23li8tweQw6odWQebz";
@@ -740,8 +741,13 @@ export function createCopilotMenuAdapter(inputDeps) {
740
741
  });
741
742
  return false;
742
743
  }
743
- if (action.name === "wechat-bind") {
744
- return true;
744
+ if (action.name === "wechat-bind" || action.name === "wechat-rebind") {
745
+ await runWechatBindFlow({
746
+ action: action.name,
747
+ readCommonSettings,
748
+ writeCommonSettings,
749
+ });
750
+ return false;
745
751
  }
746
752
  if (action.name === "list-models") {
747
753
  const modelID = await select([
package/dist/ui/menu.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { select, type MenuItem } from "./select.js";
2
2
  import { confirm } from "./confirm.js";
3
+ import { type CommonSettingsStore } from "../common-settings-store.js";
4
+ import { type OperatorBinding } from "../wechat/operator-store.js";
3
5
  export type AccountStatus = "active" | "expired" | "unknown";
4
6
  export interface AccountInfo {
5
7
  name: string;
@@ -69,8 +71,12 @@ export type MenuAction = {
69
71
  type: "toggle-experimental-slash-commands";
70
72
  } | {
71
73
  type: "toggle-network-retry";
74
+ } | {
75
+ type: "wechat-menu";
72
76
  } | {
73
77
  type: "wechat-bind";
78
+ } | {
79
+ type: "wechat-rebind";
74
80
  } | {
75
81
  type: "toggle-wechat-notifications";
76
82
  } | {
@@ -94,6 +100,42 @@ export type MenuAction = {
94
100
  };
95
101
  export type MenuLanguage = "zh" | "en";
96
102
  export type MenuProvider = "copilot" | "codex";
103
+ export type MenuWechatPrimaryBinding = {
104
+ accountId: string;
105
+ userId?: string;
106
+ name?: string;
107
+ enabled?: boolean;
108
+ configured?: boolean;
109
+ boundAt?: number;
110
+ };
111
+ export type MenuWechatOperatorBinding = {
112
+ wechatAccountId: string;
113
+ userId: string;
114
+ boundAt: number;
115
+ };
116
+ export type ShowMenuInput = {
117
+ provider?: MenuProvider;
118
+ refresh?: {
119
+ enabled: boolean;
120
+ minutes: number;
121
+ };
122
+ lastQuotaRefresh?: number;
123
+ modelAccountAssignmentCount?: number;
124
+ defaultAccountGroupCount?: number;
125
+ loopSafetyEnabled?: boolean;
126
+ loopSafetyProviderScope?: "copilot-only" | "all-models";
127
+ networkRetryEnabled?: boolean;
128
+ wechatNotificationsEnabled?: boolean;
129
+ wechatQuestionNotifyEnabled?: boolean;
130
+ wechatPermissionNotifyEnabled?: boolean;
131
+ wechatSessionErrorNotifyEnabled?: boolean;
132
+ wechatPrimaryBinding?: MenuWechatPrimaryBinding;
133
+ wechatOperatorBinding?: MenuWechatOperatorBinding;
134
+ syntheticAgentInitiatorEnabled?: boolean;
135
+ experimentalSlashCommandsEnabled?: boolean;
136
+ capabilities?: Partial<MenuCapabilities>;
137
+ language?: MenuLanguage;
138
+ };
97
139
  type MenuCapabilities = {
98
140
  importAuth: boolean;
99
141
  quota: boolean;
@@ -172,56 +214,20 @@ export declare function buildMenuItems(input: {
172
214
  wechatQuestionNotifyEnabled?: boolean;
173
215
  wechatPermissionNotifyEnabled?: boolean;
174
216
  wechatSessionErrorNotifyEnabled?: boolean;
217
+ wechatPrimaryBinding?: MenuWechatPrimaryBinding;
218
+ wechatOperatorBinding?: MenuWechatOperatorBinding;
175
219
  syntheticAgentInitiatorEnabled?: boolean;
176
220
  experimentalSlashCommandsEnabled?: boolean;
177
221
  capabilities?: Partial<MenuCapabilities>;
178
222
  language?: MenuLanguage;
179
223
  }): MenuItem<MenuAction>[];
180
- export declare function showMenu(accounts: AccountInfo[], input?: {
181
- provider?: MenuProvider;
182
- refresh?: {
183
- enabled: boolean;
184
- minutes: number;
185
- };
186
- lastQuotaRefresh?: number;
187
- modelAccountAssignmentCount?: number;
188
- defaultAccountGroupCount?: number;
189
- loopSafetyEnabled?: boolean;
190
- loopSafetyProviderScope?: "copilot-only" | "all-models";
191
- networkRetryEnabled?: boolean;
192
- wechatNotificationsEnabled?: boolean;
193
- wechatQuestionNotifyEnabled?: boolean;
194
- wechatPermissionNotifyEnabled?: boolean;
195
- wechatSessionErrorNotifyEnabled?: boolean;
196
- syntheticAgentInitiatorEnabled?: boolean;
197
- experimentalSlashCommandsEnabled?: boolean;
198
- capabilities?: Partial<MenuCapabilities>;
199
- language?: MenuLanguage;
200
- }): Promise<MenuAction>;
201
- export declare function showMenuWithDeps(accounts: AccountInfo[], input?: {
202
- provider?: MenuProvider;
203
- refresh?: {
204
- enabled: boolean;
205
- minutes: number;
206
- };
207
- lastQuotaRefresh?: number;
208
- modelAccountAssignmentCount?: number;
209
- defaultAccountGroupCount?: number;
210
- loopSafetyEnabled?: boolean;
211
- loopSafetyProviderScope?: "copilot-only" | "all-models";
212
- networkRetryEnabled?: boolean;
213
- wechatNotificationsEnabled?: boolean;
214
- wechatQuestionNotifyEnabled?: boolean;
215
- wechatPermissionNotifyEnabled?: boolean;
216
- wechatSessionErrorNotifyEnabled?: boolean;
217
- syntheticAgentInitiatorEnabled?: boolean;
218
- experimentalSlashCommandsEnabled?: boolean;
219
- capabilities?: Partial<MenuCapabilities>;
220
- language?: MenuLanguage;
221
- }, deps?: {
224
+ export declare function showMenu(accounts: AccountInfo[], input?: ShowMenuInput): Promise<MenuAction>;
225
+ export declare function showMenuWithDeps(accounts: AccountInfo[], input?: ShowMenuInput, deps?: {
222
226
  select?: typeof select;
223
227
  confirm?: typeof confirm;
224
228
  showAccountActions?: typeof showAccountActions;
229
+ readCommonSettings?: () => Promise<CommonSettingsStore>;
230
+ readOperatorBinding?: () => Promise<OperatorBinding | undefined>;
225
231
  }): Promise<MenuAction>;
226
232
  export declare function buildAccountActionItems(account: AccountInfo, input?: {
227
233
  provider?: MenuProvider;
package/dist/ui/menu.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { ANSI } from "./ansi.js";
2
2
  import { select } from "./select.js";
3
3
  import { confirm } from "./confirm.js";
4
+ import { readCommonSettingsStore } from "../common-settings-store.js";
5
+ import { readOperatorBinding } from "../wechat/operator-store.js";
4
6
  function defaultMenuCapabilities(provider) {
5
7
  if (provider === "codex") {
6
8
  return {
@@ -337,6 +339,12 @@ export function buildMenuItems(input) {
337
339
  hint: copy.retryHint,
338
340
  disabled: !capabilities.networkRetry,
339
341
  },
342
+ {
343
+ label: copy.wechatNotificationsHeading,
344
+ value: { type: "wechat-menu" },
345
+ color: "cyan",
346
+ disabled: !capabilities.wechatNotificationsMenu,
347
+ },
340
348
  ];
341
349
  const providerSettings = [
342
350
  { label: copy.providerSettingsHeading, value: { type: "cancel" }, kind: "heading" },
@@ -356,39 +364,6 @@ export function buildMenuItems(input) {
356
364
  hint: copy.syntheticInitiatorHint,
357
365
  });
358
366
  }
359
- const wechatNotifications = [
360
- { label: copy.wechatNotificationsHeading, value: { type: "cancel" }, kind: "heading" },
361
- {
362
- label: copy.wechatBind,
363
- value: { type: "wechat-bind" },
364
- color: "cyan",
365
- disabled: !capabilities.wechatNotificationsMenu,
366
- },
367
- {
368
- label: wechatNotificationsEnabled ? copy.wechatNotificationsOn : copy.wechatNotificationsOff,
369
- value: { type: "toggle-wechat-notifications" },
370
- color: "cyan",
371
- disabled: !capabilities.wechatNotificationsMenu,
372
- },
373
- {
374
- label: wechatQuestionNotifyEnabled ? copy.wechatQuestionNotifyOn : copy.wechatQuestionNotifyOff,
375
- value: { type: "toggle-wechat-question-notify" },
376
- color: "cyan",
377
- disabled: !capabilities.wechatNotificationsMenu,
378
- },
379
- {
380
- label: wechatPermissionNotifyEnabled ? copy.wechatPermissionNotifyOn : copy.wechatPermissionNotifyOff,
381
- value: { type: "toggle-wechat-permission-notify" },
382
- color: "cyan",
383
- disabled: !capabilities.wechatNotificationsMenu,
384
- },
385
- {
386
- label: wechatSessionErrorNotifyEnabled ? copy.wechatSessionErrorNotifyOn : copy.wechatSessionErrorNotifyOff,
387
- value: { type: "toggle-wechat-session-error-notify" },
388
- color: "cyan",
389
- disabled: !capabilities.wechatNotificationsMenu,
390
- },
391
- ];
392
367
  return [
393
368
  ...providerActions,
394
369
  { label: "", value: { type: "cancel" }, separator: true },
@@ -396,8 +371,6 @@ export function buildMenuItems(input) {
396
371
  { label: "", value: { type: "cancel" }, separator: true },
397
372
  ...providerSettings,
398
373
  { label: "", value: { type: "cancel" }, separator: true },
399
- ...wechatNotifications,
400
- { label: "", value: { type: "cancel" }, separator: true },
401
374
  { label: copy.accountsHeading, value: { type: "cancel" }, kind: "heading" },
402
375
  ...input.accounts.map((account) => {
403
376
  const statusBadge = getStatusBadge(account.status);
@@ -427,9 +400,114 @@ export function buildMenuItems(input) {
427
400
  { label: copy.removeAll, value: { type: "remove-all" }, color: "red" },
428
401
  ];
429
402
  }
403
+ function buildWechatSubmenuItems(copy, input) {
404
+ const backLabel = input.language === "en" ? "Back" : "返回上级";
405
+ const effectiveBinding = input.wechatPrimaryBinding
406
+ ? {
407
+ accountId: input.wechatPrimaryBinding.accountId,
408
+ userId: input.wechatPrimaryBinding.userId,
409
+ name: input.wechatPrimaryBinding.name,
410
+ enabled: input.wechatPrimaryBinding.enabled,
411
+ configured: input.wechatPrimaryBinding.configured,
412
+ boundAt: input.wechatPrimaryBinding.boundAt,
413
+ }
414
+ : input.wechatOperatorBinding
415
+ ? {
416
+ accountId: input.wechatOperatorBinding.wechatAccountId,
417
+ userId: input.wechatOperatorBinding.userId,
418
+ boundAt: input.wechatOperatorBinding.boundAt,
419
+ }
420
+ : undefined;
421
+ const bindActionType = effectiveBinding ? "wechat-rebind" : "wechat-bind";
422
+ const bindingRows = [];
423
+ if (effectiveBinding) {
424
+ const boundAtText = effectiveBinding.boundAt
425
+ ? new Date(effectiveBinding.boundAt).toLocaleString()
426
+ : "unknown";
427
+ bindingRows.push({ label: input.language === "en" ? "Current binding" : "当前绑定账号", value: { type: "cancel" }, kind: "heading" }, {
428
+ label: `accountId: ${effectiveBinding.accountId}`,
429
+ value: { type: "cancel" },
430
+ disabled: true,
431
+ }, ...(effectiveBinding.name
432
+ ? [{ label: `name: ${effectiveBinding.name}`, value: { type: "cancel" }, disabled: true }]
433
+ : []), ...(effectiveBinding.userId
434
+ ? [{ label: `userId: ${effectiveBinding.userId}`, value: { type: "cancel" }, disabled: true }]
435
+ : []), {
436
+ label: `enabled: ${effectiveBinding.enabled === true ? "true" : "false"}`,
437
+ value: { type: "cancel" },
438
+ disabled: true,
439
+ }, {
440
+ label: `configured: ${effectiveBinding.configured === true ? "true" : "false"}`,
441
+ value: { type: "cancel" },
442
+ disabled: true,
443
+ }, {
444
+ label: `boundAt: ${boundAtText}`,
445
+ value: { type: "cancel" },
446
+ disabled: true,
447
+ }, { label: "", value: { type: "cancel" }, separator: true });
448
+ }
449
+ return [
450
+ { label: backLabel, value: { type: "cancel" } },
451
+ { label: "", value: { type: "cancel" }, separator: true },
452
+ ...bindingRows,
453
+ { label: copy.wechatNotificationsHeading, value: { type: "cancel" }, kind: "heading" },
454
+ {
455
+ label: copy.wechatBind,
456
+ value: { type: bindActionType },
457
+ color: "cyan",
458
+ disabled: !input.capabilities.wechatNotificationsMenu,
459
+ },
460
+ {
461
+ label: input.wechatNotificationsEnabled ? copy.wechatNotificationsOn : copy.wechatNotificationsOff,
462
+ value: { type: "toggle-wechat-notifications" },
463
+ color: "cyan",
464
+ disabled: !input.capabilities.wechatNotificationsMenu,
465
+ },
466
+ {
467
+ label: input.wechatQuestionNotifyEnabled ? copy.wechatQuestionNotifyOn : copy.wechatQuestionNotifyOff,
468
+ value: { type: "toggle-wechat-question-notify" },
469
+ color: "cyan",
470
+ disabled: !input.capabilities.wechatNotificationsMenu,
471
+ },
472
+ {
473
+ label: input.wechatPermissionNotifyEnabled ? copy.wechatPermissionNotifyOn : copy.wechatPermissionNotifyOff,
474
+ value: { type: "toggle-wechat-permission-notify" },
475
+ color: "cyan",
476
+ disabled: !input.capabilities.wechatNotificationsMenu,
477
+ },
478
+ {
479
+ label: input.wechatSessionErrorNotifyEnabled ? copy.wechatSessionErrorNotifyOn : copy.wechatSessionErrorNotifyOff,
480
+ value: { type: "toggle-wechat-session-error-notify" },
481
+ color: "cyan",
482
+ disabled: !input.capabilities.wechatNotificationsMenu,
483
+ },
484
+ ];
485
+ }
430
486
  export async function showMenu(accounts, input = {}) {
431
487
  return showMenuWithDeps(accounts, input);
432
488
  }
489
+ function pickPrimaryBindingFromSettings(settings) {
490
+ const primary = settings?.wechat?.primaryBinding;
491
+ if (!primary?.accountId)
492
+ return undefined;
493
+ return {
494
+ accountId: primary.accountId,
495
+ userId: primary.userId,
496
+ name: primary.name,
497
+ enabled: primary.enabled,
498
+ configured: primary.configured,
499
+ boundAt: primary.boundAt,
500
+ };
501
+ }
502
+ function pickOperatorBinding(binding) {
503
+ if (!binding)
504
+ return undefined;
505
+ return {
506
+ wechatAccountId: binding.wechatAccountId,
507
+ userId: binding.userId,
508
+ boundAt: binding.boundAt,
509
+ };
510
+ }
433
511
  export async function showMenuWithDeps(accounts, input = {}, deps = {}) {
434
512
  const selectMenu = deps.select ?? select;
435
513
  const confirmAction = deps.confirm ?? confirm;
@@ -464,6 +542,38 @@ export async function showMenuWithDeps(accounts, input = {}, deps = {}) {
464
542
  });
465
543
  if (!result)
466
544
  return { type: "cancel" };
545
+ if (result.type === "wechat-menu") {
546
+ const [commonSettings, operatorBinding] = await Promise.all([
547
+ input.wechatPrimaryBinding
548
+ ? Promise.resolve(undefined)
549
+ : (deps.readCommonSettings ?? readCommonSettingsStore)().catch(() => undefined),
550
+ input.wechatOperatorBinding
551
+ ? Promise.resolve(undefined)
552
+ : (deps.readOperatorBinding ?? readOperatorBinding)().catch(() => undefined),
553
+ ]);
554
+ const wechatItems = buildWechatSubmenuItems(copy, {
555
+ wechatNotificationsEnabled: input.wechatNotificationsEnabled !== false,
556
+ wechatQuestionNotifyEnabled: input.wechatQuestionNotifyEnabled !== false,
557
+ wechatPermissionNotifyEnabled: input.wechatPermissionNotifyEnabled !== false,
558
+ wechatSessionErrorNotifyEnabled: input.wechatSessionErrorNotifyEnabled !== false,
559
+ wechatPrimaryBinding: input.wechatPrimaryBinding ?? pickPrimaryBindingFromSettings(commonSettings),
560
+ wechatOperatorBinding: input.wechatOperatorBinding ?? pickOperatorBinding(operatorBinding),
561
+ capabilities: {
562
+ ...defaultMenuCapabilities(provider),
563
+ ...input.capabilities,
564
+ },
565
+ language: currentLanguage,
566
+ });
567
+ const wechatResult = await selectMenu(wechatItems, {
568
+ message: copy.wechatNotificationsHeading,
569
+ subtitle: copy.menuSubtitle,
570
+ clearScreen: true,
571
+ });
572
+ if (!wechatResult || wechatResult.type === "cancel") {
573
+ continue;
574
+ }
575
+ return wechatResult;
576
+ }
467
577
  if (result.type === "toggle-language") {
468
578
  currentLanguage = currentLanguage === "zh" ? "en" : "zh";
469
579
  continue;
@@ -0,0 +1,25 @@
1
+ import { bindOperator, readOperatorBinding, rebindOperator, resetOperatorBinding } from "./operator-store.js";
2
+ import { loadOpenClawWeixinPublicHelpers } from "./compat/openclaw-public-helpers.js";
3
+ import type { CommonSettingsStore } from "../common-settings-store.js";
4
+ type BindAction = "wechat-bind" | "wechat-rebind";
5
+ type WechatBindFlowResult = {
6
+ accountId: string;
7
+ userId: string;
8
+ name?: string;
9
+ enabled?: boolean;
10
+ configured?: boolean;
11
+ boundAt: number;
12
+ };
13
+ type WechatBindFlowInput = {
14
+ action: BindAction;
15
+ loadPublicHelpers?: typeof loadOpenClawWeixinPublicHelpers;
16
+ bindOperator?: typeof bindOperator;
17
+ rebindOperator?: typeof rebindOperator;
18
+ readOperatorBinding?: typeof readOperatorBinding;
19
+ resetOperatorBinding?: typeof resetOperatorBinding;
20
+ readCommonSettings: () => Promise<CommonSettingsStore>;
21
+ writeCommonSettings: (settings: CommonSettingsStore) => Promise<void>;
22
+ now?: () => number;
23
+ };
24
+ export declare function runWechatBindFlow(input: WechatBindFlowInput): Promise<WechatBindFlowResult>;
25
+ export {};
@@ -0,0 +1,101 @@
1
+ import { bindOperator, readOperatorBinding, rebindOperator, resetOperatorBinding } from "./operator-store.js";
2
+ import { loadOpenClawWeixinPublicHelpers } from "./compat/openclaw-public-helpers.js";
3
+ import { buildOpenClawMenuAccount } from "./openclaw-account-adapter.js";
4
+ function pickFirstNonEmptyString(...values) {
5
+ for (const value of values) {
6
+ if (typeof value === "string" && value.trim().length > 0) {
7
+ return value;
8
+ }
9
+ }
10
+ return undefined;
11
+ }
12
+ function toErrorMessage(error) {
13
+ if (error instanceof Error && error.message.trim().length > 0) {
14
+ return error.message;
15
+ }
16
+ return String(error);
17
+ }
18
+ export async function runWechatBindFlow(input) {
19
+ const now = input.now ?? Date.now;
20
+ const loadPublicHelpers = input.loadPublicHelpers ?? loadOpenClawWeixinPublicHelpers;
21
+ const persistOperatorBinding = input.bindOperator ?? bindOperator;
22
+ const persistOperatorRebinding = input.rebindOperator ?? rebindOperator;
23
+ const loadOperatorBinding = input.readOperatorBinding ?? readOperatorBinding;
24
+ const clearOperatorBinding = input.resetOperatorBinding ?? resetOperatorBinding;
25
+ try {
26
+ const helpers = await loadPublicHelpers();
27
+ const started = await Promise.resolve(helpers.qrGateway.loginWithQrStart({ source: "menu", action: input.action }));
28
+ const sessionKey = pickFirstNonEmptyString(started?.sessionKey, started?.key);
29
+ const waited = await Promise.resolve(helpers.qrGateway.loginWithQrWait({ timeoutMs: 120000, sessionKey }));
30
+ const accountId = pickFirstNonEmptyString(helpers.latestAccountState?.accountId, waited?.accountId, (await helpers.accountHelpers.listAccountIds()).at(-1));
31
+ const userId = pickFirstNonEmptyString(waited?.userId, waited?.openid, waited?.uid);
32
+ if (!accountId) {
33
+ throw new Error("missing accountId after qr login");
34
+ }
35
+ if (!userId) {
36
+ throw new Error("missing userId after qr login");
37
+ }
38
+ const boundAt = now();
39
+ const operatorBinding = {
40
+ wechatAccountId: accountId,
41
+ userId,
42
+ boundAt,
43
+ };
44
+ const previousOperatorBinding = input.action === "wechat-rebind" ? await loadOperatorBinding() : undefined;
45
+ if (input.action === "wechat-rebind") {
46
+ await persistOperatorRebinding(operatorBinding);
47
+ }
48
+ else {
49
+ await persistOperatorBinding(operatorBinding);
50
+ }
51
+ const menuAccount = await buildOpenClawMenuAccount({
52
+ latestAccountState: helpers.latestAccountState,
53
+ accountHelpers: helpers.accountHelpers,
54
+ });
55
+ const settings = await input.readCommonSettings();
56
+ const notifications = settings.wechat?.notifications ?? {
57
+ enabled: true,
58
+ question: true,
59
+ permission: true,
60
+ sessionError: true,
61
+ };
62
+ settings.wechat = {
63
+ ...settings.wechat,
64
+ notifications,
65
+ primaryBinding: {
66
+ accountId,
67
+ userId,
68
+ name: menuAccount?.name,
69
+ enabled: menuAccount?.enabled,
70
+ configured: menuAccount?.configured,
71
+ boundAt,
72
+ },
73
+ };
74
+ try {
75
+ await input.writeCommonSettings(settings);
76
+ }
77
+ catch (error) {
78
+ if (input.action === "wechat-rebind" && previousOperatorBinding) {
79
+ await persistOperatorRebinding(previousOperatorBinding).catch(() => { });
80
+ }
81
+ else {
82
+ await clearOperatorBinding().catch(() => { });
83
+ }
84
+ throw error;
85
+ }
86
+ return {
87
+ accountId,
88
+ userId,
89
+ name: menuAccount?.name,
90
+ enabled: menuAccount?.enabled,
91
+ configured: menuAccount?.configured,
92
+ boundAt,
93
+ };
94
+ }
95
+ catch (error) {
96
+ if (input.action === "wechat-rebind") {
97
+ throw new Error(`wechat rebind failed: ${toErrorMessage(error)}`);
98
+ }
99
+ throw new Error(`wechat bind failed: ${toErrorMessage(error)}`);
100
+ }
101
+ }
@@ -8,6 +8,13 @@ type WeixinQrGateway = {
8
8
  loginWithQrStart: (input?: unknown) => unknown;
9
9
  loginWithQrWait: (input?: unknown) => unknown;
10
10
  };
11
+ export type WeixinAccountHelpers = {
12
+ listAccountIds: () => Promise<string[]>;
13
+ resolveAccount: (accountId: string) => Promise<unknown>;
14
+ describeAccount: (accountIdOrInput: string | {
15
+ accountId: string;
16
+ }) => Promise<unknown>;
17
+ };
11
18
  export type OpenClawWeixinPublicEntry = {
12
19
  packageJsonPath: string;
13
20
  packageRoot: string;
@@ -16,6 +23,7 @@ export type OpenClawWeixinPublicEntry = {
16
23
  entryAbsolutePath: string;
17
24
  };
18
25
  declare function resolveOpenClawWeixinPublicEntry(): Promise<OpenClawWeixinPublicEntry>;
26
+ declare function loadPublicWeixinAccountHelpers(): Promise<WeixinAccountHelpers>;
19
27
  declare function loadLatestWeixinAccountState(): Promise<{
20
28
  accountId: string;
21
29
  token: string;
@@ -54,6 +62,7 @@ export type OpenClawWeixinPublicHelpers = {
54
62
  entry: OpenClawWeixinPublicEntry;
55
63
  pluginId: string;
56
64
  qrGateway: WeixinQrGateway;
65
+ accountHelpers: WeixinAccountHelpers;
57
66
  latestAccountState: {
58
67
  accountId: string;
59
68
  token: string;
@@ -79,6 +88,7 @@ type OpenClawWeixinPublicHelpersLoaders = {
79
88
  pluginId?: string;
80
89
  }>;
81
90
  loadLatestWeixinAccountState?: typeof loadLatestWeixinAccountState;
91
+ loadPublicWeixinAccountHelpers?: typeof loadPublicWeixinAccountHelpers;
82
92
  loadPublicWeixinHelpers?: typeof loadPublicWeixinHelpers;
83
93
  loadPublicWeixinSendHelper?: typeof loadPublicWeixinSendHelper;
84
94
  };
@@ -31,6 +31,15 @@ function hasQrLoginMethods(value) {
31
31
  const candidate = value;
32
32
  return typeof candidate.loginWithQrStart === "function" && typeof candidate.loginWithQrWait === "function";
33
33
  }
34
+ function hasAccountHelpers(value) {
35
+ if (!value || typeof value !== "object") {
36
+ return false;
37
+ }
38
+ const candidate = value;
39
+ return (typeof candidate.listAccountIds === "function" &&
40
+ typeof candidate.resolveAccount === "function" &&
41
+ typeof candidate.describeAccount === "function");
42
+ }
34
43
  async function resolveOpenClawWeixinPublicEntry() {
35
44
  const require = createRequire(import.meta.url);
36
45
  const packageName = "@tencent-weixin/openclaw-weixin";
@@ -94,6 +103,45 @@ async function loadPublicWeixinQrGateway() {
94
103
  }
95
104
  throw new Error("registerChannel did not expose weixin gateway loginWithQrStart/loginWithQrWait");
96
105
  }
106
+ async function loadPublicWeixinAccountHelpers() {
107
+ const registeredPayloads = [];
108
+ const compatHostApi = {
109
+ runtime: {
110
+ channelRuntime: {
111
+ mode: "guided-smoke",
112
+ },
113
+ gateway: {
114
+ startAccount: {
115
+ source: "guided-smoke",
116
+ },
117
+ },
118
+ },
119
+ registerChannel(payload) {
120
+ registeredPayloads.push(payload);
121
+ },
122
+ registerCli() { },
123
+ };
124
+ const plugin = await loadOpenClawWeixinDefaultExport();
125
+ plugin.register(compatHostApi);
126
+ for (const payload of registeredPayloads) {
127
+ const payloadPlugin = payload?.plugin;
128
+ const configCandidate = payloadPlugin && typeof payloadPlugin === "object"
129
+ ? payloadPlugin.config
130
+ : null;
131
+ if (!hasAccountHelpers(configCandidate)) {
132
+ continue;
133
+ }
134
+ return {
135
+ listAccountIds: async () => {
136
+ const result = await Promise.resolve(configCandidate.listAccountIds());
137
+ return Array.isArray(result) ? result.filter((item) => typeof item === "string") : [];
138
+ },
139
+ resolveAccount: async (accountId) => Promise.resolve(configCandidate.resolveAccount(accountId)),
140
+ describeAccount: async (accountIdOrInput) => Promise.resolve(configCandidate.describeAccount(accountIdOrInput)),
141
+ };
142
+ }
143
+ throw new Error(`${plugin.id ?? "weixin"} registerChannel did not expose config listAccountIds/resolveAccount/describeAccount`);
144
+ }
97
145
  async function loadLatestWeixinAccountState() {
98
146
  const require = createRequire(import.meta.url);
99
147
  const stateDirModulePath = require.resolve(OPENCLAW_WEIXIN_JITI_SRC_HELPER_MODULES.stateDir);
@@ -182,6 +230,7 @@ async function loadPublicWeixinSyncBufHelpers() {
182
230
  export async function loadOpenClawWeixinPublicHelpers(loaders = {}) {
183
231
  const entry = await (loaders.resolveOpenClawWeixinPublicEntry ?? resolveOpenClawWeixinPublicEntry)();
184
232
  const qrGatewayResult = await (loaders.loadPublicWeixinQrGateway ?? loadPublicWeixinQrGateway)();
233
+ const accountHelpers = await (loaders.loadPublicWeixinAccountHelpers ?? loadPublicWeixinAccountHelpers)();
185
234
  const latestAccountState = await (loaders.loadLatestWeixinAccountState ?? loadLatestWeixinAccountState)();
186
235
  const publicHelpers = await (loaders.loadPublicWeixinHelpers ?? loadPublicWeixinHelpers)();
187
236
  const sendHelper = await (loaders.loadPublicWeixinSendHelper ?? loadPublicWeixinSendHelper)();
@@ -192,6 +241,11 @@ export async function loadOpenClawWeixinPublicHelpers(loaders = {}) {
192
241
  if (typeof publicHelpers?.getUpdates !== "function") {
193
242
  throw missingHelperError("getUpdates");
194
243
  }
244
+ if (typeof accountHelpers?.listAccountIds !== "function" ||
245
+ typeof accountHelpers?.resolveAccount !== "function" ||
246
+ typeof accountHelpers?.describeAccount !== "function") {
247
+ throw missingHelperError("accountHelpers");
248
+ }
195
249
  if (typeof sendHelper?.sendMessageWeixin !== "function") {
196
250
  throw missingHelperError("sendMessageWeixin");
197
251
  }
@@ -199,6 +253,7 @@ export async function loadOpenClawWeixinPublicHelpers(loaders = {}) {
199
253
  entry,
200
254
  pluginId: typeof qrGatewayResult.pluginId === "string" && qrGatewayResult.pluginId.length > 0 ? qrGatewayResult.pluginId : "unknown",
201
255
  qrGateway: qrGatewayResult.gateway,
256
+ accountHelpers,
202
257
  latestAccountState,
203
258
  getUpdates: publicHelpers.getUpdates,
204
259
  sendMessageWeixin: sendHelper.sendMessageWeixin,
@@ -0,0 +1,30 @@
1
+ export type OpenClawWeixinAccountHelpers = {
2
+ listAccountIds: () => Promise<string[]>;
3
+ resolveAccount: (accountId: string) => Promise<unknown>;
4
+ describeAccount: (accountIdOrInput: string | {
5
+ accountId: string;
6
+ }) => Promise<unknown>;
7
+ };
8
+ export type OpenClawLatestAccountState = {
9
+ accountId: string;
10
+ token: string;
11
+ baseUrl: string;
12
+ getUpdatesBuf?: string;
13
+ userId?: string;
14
+ savedAt?: number;
15
+ boundAt?: number;
16
+ };
17
+ export type OpenClawMenuAccount = {
18
+ accountId: string;
19
+ name?: string;
20
+ enabled: boolean;
21
+ configured: boolean;
22
+ userId?: string;
23
+ boundAt?: number;
24
+ };
25
+ type BuildOpenClawMenuAccountInput = {
26
+ latestAccountState: OpenClawLatestAccountState | null;
27
+ accountHelpers: OpenClawWeixinAccountHelpers;
28
+ };
29
+ export declare function buildOpenClawMenuAccount(input: BuildOpenClawMenuAccountInput): Promise<OpenClawMenuAccount | null>;
30
+ export {};
@@ -0,0 +1,70 @@
1
+ function asObject(value) {
2
+ return value && typeof value === "object" ? value : {};
3
+ }
4
+ function firstNonEmptyString(...values) {
5
+ for (const value of values) {
6
+ if (typeof value === "string" && value.trim().length > 0) {
7
+ return value;
8
+ }
9
+ }
10
+ return undefined;
11
+ }
12
+ function toBoolean(value) {
13
+ if (typeof value === "boolean") {
14
+ return value;
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
+ return undefined;
31
+ }
32
+ async function tryDescribeAccount(accountHelpers, accountId) {
33
+ try {
34
+ const byString = await accountHelpers.describeAccount(accountId);
35
+ if (byString !== undefined) {
36
+ return byString;
37
+ }
38
+ }
39
+ catch {
40
+ // fallback to object input
41
+ }
42
+ try {
43
+ return await accountHelpers.describeAccount({ accountId });
44
+ }
45
+ catch {
46
+ return undefined;
47
+ }
48
+ }
49
+ export async function buildOpenClawMenuAccount(input) {
50
+ const fallbackAccountId = firstNonEmptyString(input.latestAccountState?.accountId);
51
+ const accountIds = await input.accountHelpers.listAccountIds();
52
+ const accountId = firstNonEmptyString(fallbackAccountId, accountIds.at(-1));
53
+ if (!accountId) {
54
+ return null;
55
+ }
56
+ const resolvedRaw = asObject(await input.accountHelpers.resolveAccount(accountId));
57
+ 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);
62
+ return {
63
+ accountId,
64
+ name: firstNonEmptyString(resolvedRaw.name, resolvedRaw.displayName, describedRaw.name, describedRaw.displayName),
65
+ enabled,
66
+ configured,
67
+ userId,
68
+ boundAt,
69
+ };
70
+ }
@@ -5,4 +5,5 @@ export type OperatorBinding = {
5
5
  };
6
6
  export declare function readOperatorBinding(): Promise<OperatorBinding | undefined>;
7
7
  export declare function bindOperator(input: OperatorBinding): Promise<OperatorBinding>;
8
+ export declare function rebindOperator(input: OperatorBinding): Promise<OperatorBinding>;
8
9
  export declare function resetOperatorBinding(): Promise<void>;
@@ -47,6 +47,16 @@ export async function bindOperator(input) {
47
47
  await writeFile(operatorStatePath(), JSON.stringify(next, null, 2), { mode: WECHAT_FILE_MODE });
48
48
  return next;
49
49
  }
50
+ export async function rebindOperator(input) {
51
+ const next = toBinding(input);
52
+ if (!isValidBinding(next)) {
53
+ throw new Error("invalid operator binding format");
54
+ }
55
+ await ensureWechatStateLayout();
56
+ await mkdir(path.dirname(operatorStatePath()), { recursive: true });
57
+ await writeFile(operatorStatePath(), JSON.stringify(next, null, 2), { mode: WECHAT_FILE_MODE });
58
+ return next;
59
+ }
50
60
  export async function resetOperatorBinding() {
51
61
  try {
52
62
  await unlink(operatorStatePath());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-copilot-account-switcher",
3
- "version": "0.14.0",
3
+ "version": "0.14.2",
4
4
  "description": "GitHub Copilot account switcher plugin for OpenCode",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",