opencode-copilot-account-switcher 0.13.6 → 0.14.1

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 (58) hide show
  1. package/dist/common-settings-actions.d.ts +1 -1
  2. package/dist/common-settings-actions.js +54 -0
  3. package/dist/common-settings-store.d.ts +25 -0
  4. package/dist/common-settings-store.js +81 -0
  5. package/dist/menu-runtime.js +12 -1
  6. package/dist/plugin-hooks.d.ts +8 -0
  7. package/dist/plugin-hooks.js +96 -0
  8. package/dist/providers/codex-menu-adapter.js +14 -1
  9. package/dist/providers/copilot-menu-adapter.js +13 -0
  10. package/dist/store-paths.d.ts +1 -0
  11. package/dist/store-paths.js +3 -0
  12. package/dist/ui/menu.d.ts +73 -34
  13. package/dist/ui/menu.js +195 -0
  14. package/dist/wechat/bind-flow.d.ts +25 -0
  15. package/dist/wechat/bind-flow.js +101 -0
  16. package/dist/wechat/bridge.d.ts +69 -0
  17. package/dist/wechat/bridge.js +180 -0
  18. package/dist/wechat/broker-client.d.ts +33 -0
  19. package/dist/wechat/broker-client.js +257 -0
  20. package/dist/wechat/broker-entry.d.ts +17 -0
  21. package/dist/wechat/broker-entry.js +182 -0
  22. package/dist/wechat/broker-launcher.d.ts +27 -0
  23. package/dist/wechat/broker-launcher.js +191 -0
  24. package/dist/wechat/broker-server.d.ts +25 -0
  25. package/dist/wechat/broker-server.js +540 -0
  26. package/dist/wechat/command-parser.d.ts +7 -0
  27. package/dist/wechat/command-parser.js +16 -0
  28. package/dist/wechat/compat/openclaw-guided-smoke.d.ts +178 -0
  29. package/dist/wechat/compat/openclaw-guided-smoke.js +1133 -0
  30. package/dist/wechat/compat/openclaw-public-helpers.d.ts +111 -0
  31. package/dist/wechat/compat/openclaw-public-helpers.js +262 -0
  32. package/dist/wechat/compat/openclaw-smoke.d.ts +48 -0
  33. package/dist/wechat/compat/openclaw-smoke.js +100 -0
  34. package/dist/wechat/compat/slash-guard.d.ts +11 -0
  35. package/dist/wechat/compat/slash-guard.js +24 -0
  36. package/dist/wechat/handle.d.ts +8 -0
  37. package/dist/wechat/handle.js +46 -0
  38. package/dist/wechat/ipc-auth.d.ts +6 -0
  39. package/dist/wechat/ipc-auth.js +39 -0
  40. package/dist/wechat/openclaw-account-adapter.d.ts +30 -0
  41. package/dist/wechat/openclaw-account-adapter.js +70 -0
  42. package/dist/wechat/operator-store.d.ts +9 -0
  43. package/dist/wechat/operator-store.js +69 -0
  44. package/dist/wechat/protocol.d.ts +29 -0
  45. package/dist/wechat/protocol.js +75 -0
  46. package/dist/wechat/request-store.d.ts +41 -0
  47. package/dist/wechat/request-store.js +215 -0
  48. package/dist/wechat/session-digest.d.ts +41 -0
  49. package/dist/wechat/session-digest.js +134 -0
  50. package/dist/wechat/state-paths.d.ts +14 -0
  51. package/dist/wechat/state-paths.js +45 -0
  52. package/dist/wechat/status-format.d.ts +14 -0
  53. package/dist/wechat/status-format.js +174 -0
  54. package/dist/wechat/token-store.d.ts +18 -0
  55. package/dist/wechat/token-store.js +100 -0
  56. package/dist/wechat/wechat-status-runtime.d.ts +24 -0
  57. package/dist/wechat/wechat-status-runtime.js +238 -0
  58. package/package.json +8 -3
@@ -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";
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, {
@@ -38,5 +53,44 @@ export async function applyCommonSettingsAction(input) {
38
53
  });
39
54
  return true;
40
55
  }
56
+ if (input.action.type === "toggle-wechat-notifications") {
57
+ settings.wechat.notifications.enabled = settings.wechat.notifications.enabled !== true;
58
+ await input.writeSettings(settings, {
59
+ reason: "toggle-wechat-notifications",
60
+ source: "applyCommonSettingsAction",
61
+ actionType: "toggle-wechat-notifications",
62
+ });
63
+ return true;
64
+ }
65
+ if (input.action.type === "toggle-wechat-question-notify") {
66
+ settings.wechat.notifications.question = settings.wechat.notifications.question !== true;
67
+ await input.writeSettings(settings, {
68
+ reason: "toggle-wechat-question-notify",
69
+ source: "applyCommonSettingsAction",
70
+ actionType: "toggle-wechat-question-notify",
71
+ });
72
+ return true;
73
+ }
74
+ if (input.action.type === "toggle-wechat-permission-notify") {
75
+ settings.wechat.notifications.permission = settings.wechat.notifications.permission !== true;
76
+ await input.writeSettings(settings, {
77
+ reason: "toggle-wechat-permission-notify",
78
+ source: "applyCommonSettingsAction",
79
+ actionType: "toggle-wechat-permission-notify",
80
+ });
81
+ return true;
82
+ }
83
+ if (input.action.type === "toggle-wechat-session-error-notify") {
84
+ settings.wechat.notifications.sessionError = settings.wechat.notifications.sessionError !== true;
85
+ await input.writeSettings(settings, {
86
+ reason: "toggle-wechat-session-error-notify",
87
+ source: "applyCommonSettingsAction",
88
+ actionType: "toggle-wechat-session-error-notify",
89
+ });
90
+ return true;
91
+ }
92
+ if (input.action.type === "wechat-bind" || input.action.type === "wechat-rebind" || input.action.type === "wechat-unbind") {
93
+ return true;
94
+ }
41
95
  return false;
42
96
  }
@@ -4,6 +4,31 @@ export type CommonSettingsStore = {
4
4
  networkRetryEnabled?: boolean;
5
5
  experimentalSlashCommandsEnabled?: boolean;
6
6
  experimentalStatusSlashCommandEnabled?: boolean;
7
+ wechat?: WechatMenuSettings;
8
+ wechatNotificationsEnabled?: boolean;
9
+ wechatQuestionNotifyEnabled?: boolean;
10
+ wechatPermissionNotifyEnabled?: boolean;
11
+ wechatSessionErrorNotifyEnabled?: boolean;
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
+ };
7
32
  };
8
33
  export declare function parseCommonSettingsStore(raw: string): CommonSettingsStore;
9
34
  export declare function commonSettingsPath(): string;
@@ -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,6 +74,7 @@ function normalizeCommonSettingsStore(input) {
15
74
  : legacySlashCommandsEnabled === false
16
75
  ? false
17
76
  : true,
77
+ wechat,
18
78
  };
19
79
  }
20
80
  function parsePartialCommonSettingsStore(raw) {
@@ -35,6 +95,21 @@ function parsePartialCommonSettingsStore(raw) {
35
95
  if (parsed.experimentalStatusSlashCommandEnabled === true || parsed.experimentalStatusSlashCommandEnabled === false) {
36
96
  partial.experimentalStatusSlashCommandEnabled = parsed.experimentalStatusSlashCommandEnabled;
37
97
  }
98
+ if (parsed.wechat && typeof parsed.wechat === "object") {
99
+ partial.wechat = parsed.wechat;
100
+ }
101
+ if (parsed.wechatNotificationsEnabled === true || parsed.wechatNotificationsEnabled === false) {
102
+ partial.wechatNotificationsEnabled = parsed.wechatNotificationsEnabled;
103
+ }
104
+ if (parsed.wechatQuestionNotifyEnabled === true || parsed.wechatQuestionNotifyEnabled === false) {
105
+ partial.wechatQuestionNotifyEnabled = parsed.wechatQuestionNotifyEnabled;
106
+ }
107
+ if (parsed.wechatPermissionNotifyEnabled === true || parsed.wechatPermissionNotifyEnabled === false) {
108
+ partial.wechatPermissionNotifyEnabled = parsed.wechatPermissionNotifyEnabled;
109
+ }
110
+ if (parsed.wechatSessionErrorNotifyEnabled === true || parsed.wechatSessionErrorNotifyEnabled === false) {
111
+ partial.wechatSessionErrorNotifyEnabled = parsed.wechatSessionErrorNotifyEnabled;
112
+ }
38
113
  return partial;
39
114
  }
40
115
  function readLegacyCommonSettings(store) {
@@ -55,6 +130,11 @@ function mergeCommonSettings(current, legacy) {
55
130
  networkRetryEnabled: current.networkRetryEnabled ?? legacy.networkRetryEnabled,
56
131
  experimentalSlashCommandsEnabled: current.experimentalSlashCommandsEnabled ?? legacy.experimentalSlashCommandsEnabled,
57
132
  experimentalStatusSlashCommandEnabled: current.experimentalStatusSlashCommandEnabled ?? legacy.experimentalStatusSlashCommandEnabled,
133
+ wechat: current.wechat,
134
+ wechatNotificationsEnabled: current.wechatNotificationsEnabled ?? legacy.wechatNotificationsEnabled,
135
+ wechatQuestionNotifyEnabled: current.wechatQuestionNotifyEnabled ?? legacy.wechatQuestionNotifyEnabled,
136
+ wechatPermissionNotifyEnabled: current.wechatPermissionNotifyEnabled ?? legacy.wechatPermissionNotifyEnabled,
137
+ wechatSessionErrorNotifyEnabled: current.wechatSessionErrorNotifyEnabled ?? legacy.wechatSessionErrorNotifyEnabled,
58
138
  });
59
139
  }
60
140
  export function parseCommonSettingsStore(raw) {
@@ -116,6 +196,7 @@ export async function writeCommonSettingsStore(store, options) {
116
196
  loopSafetyProviderScope: normalized.loopSafetyProviderScope,
117
197
  networkRetryEnabled: normalized.networkRetryEnabled,
118
198
  experimentalSlashCommandsEnabled: normalized.experimentalSlashCommandsEnabled,
199
+ wechat: normalized.wechat,
119
200
  };
120
201
  await fs.mkdir(path.dirname(file), { recursive: true });
121
202
  await fs.writeFile(file, JSON.stringify(persisted, null, 2), { mode: 0o600 });
@@ -10,6 +10,15 @@ function parseSharedActionResult(result) {
10
10
  persistHandled: false,
11
11
  };
12
12
  }
13
+ function providerActionReason(name) {
14
+ if (name.startsWith("wechat-")) {
15
+ return `wechat-action:${name}`;
16
+ }
17
+ return `provider-action:${name}`;
18
+ }
19
+ function isNonPersistentProviderAction(name) {
20
+ return name === "wechat-bind";
21
+ }
13
22
  export async function runProviderMenu(input) {
14
23
  const now = input.now ?? Date.now;
15
24
  const store = await input.adapter.loadStore();
@@ -99,8 +108,10 @@ export async function runProviderMenu(input) {
99
108
  continue;
100
109
  if (!await input.adapter.applyAction(store, action))
101
110
  continue;
111
+ if (isNonPersistentProviderAction(action.name))
112
+ continue;
102
113
  await input.adapter.writeStore(store, {
103
- reason: `provider-action:${action.name}`,
114
+ reason: providerActionReason(action.name),
104
115
  source: "menu-runtime",
105
116
  actionType: action.name,
106
117
  });
@@ -9,6 +9,7 @@ import { handleStatusCommand } from "./status-command.js";
9
9
  import { handleCodexStatusCommand } from "./codex-status-command.js";
10
10
  import { handleCompactCommand, handleStopToolCommand } from "./session-control-command.js";
11
11
  import { type AppendSessionTouchEventInput, type RouteDecisionEvent, type RoutingSnapshot, type RoutingEvent } from "./routing-state.js";
12
+ import { type WechatBridgeLifecycleInput } from "./wechat/bridge.js";
12
13
  type ChatHeadersHook = (input: {
13
14
  sessionID: string;
14
15
  agent: string;
@@ -81,8 +82,15 @@ export declare function buildPluginHooks(input: {
81
82
  }) => Promise<OfficialChatHeadersHook>;
82
83
  createRetryFetch?: (fetch: FetchLike, ctx?: CopilotRetryContext) => FetchLike;
83
84
  client?: CopilotRetryContext["client"];
85
+ project?: {
86
+ id?: string;
87
+ name?: string;
88
+ };
84
89
  directory?: CopilotRetryContext["directory"];
85
90
  serverUrl?: CopilotRetryContext["serverUrl"];
91
+ createWechatBridgeLifecycleImpl?: (input: WechatBridgeLifecycleInput) => Promise<{
92
+ close: () => Promise<void>;
93
+ }>;
86
94
  clearAccountSwitchContext?: (lastAccountSwitchAt?: number) => Promise<void>;
87
95
  now?: () => number;
88
96
  refreshQuota?: RefreshQuota;
@@ -19,6 +19,7 @@ import { handleStatusCommand, showStatusToast } from "./status-command.js";
19
19
  import { handleCodexStatusCommand } from "./codex-status-command.js";
20
20
  import { handleCompactCommand, handleStopToolCommand, } from "./session-control-command.js";
21
21
  import { appendRoutingEvent, appendRouteDecisionEvent, appendSessionTouchEvent, buildCandidateAccountLoads, isAccountRateLimitCooledDown, readRoutingState, routingStatePath, } from "./routing-state.js";
22
+ import { createWechatBridgeLifecycle, } from "./wechat/bridge.js";
22
23
  const SESSION_BINDING_IDLE_TTL_MS = 30 * 60 * 1000;
23
24
  const RATE_LIMIT_WINDOW_MS = 5 * 60 * 1000;
24
25
  const RATE_LIMIT_HIT_THRESHOLD = 3;
@@ -27,6 +28,70 @@ const MAX_SESSION_BINDINGS = 256;
27
28
  const TOUCH_WRITE_CACHE_IDLE_TTL_MS = 30 * 60 * 1000;
28
29
  const MAX_TOUCH_WRITE_CACHE_ENTRIES = 2048;
29
30
  const INTERNAL_DEBUG_LINK_HEADER = "x-opencode-debug-link-id";
31
+ let wechatBridgeLifecycleState;
32
+ let wechatBridgeAutoCloseAttached = false;
33
+ function buildWechatBridgeLifecycleKey(input) {
34
+ const projectName = typeof input.project?.name === "string" ? input.project.name : "";
35
+ const projectId = typeof input.project?.id === "string" ? input.project.id : "";
36
+ const directory = typeof input.directory === "string" ? input.directory : "";
37
+ const serverUrl = input.serverUrl?.href ?? "";
38
+ return `${serverUrl}|${directory}|${projectName}|${projectId}`;
39
+ }
40
+ function attachWechatBridgeAutoClose() {
41
+ if (wechatBridgeAutoCloseAttached) {
42
+ return;
43
+ }
44
+ wechatBridgeAutoCloseAttached = true;
45
+ const closeLifecycle = () => {
46
+ const state = wechatBridgeLifecycleState;
47
+ if (!state)
48
+ return;
49
+ closeWechatBridgeLifecycleState(state);
50
+ };
51
+ process.once("beforeExit", closeLifecycle);
52
+ process.once("SIGINT", closeLifecycle);
53
+ process.once("SIGTERM", closeLifecycle);
54
+ }
55
+ function closeWechatBridgeLifecycleState(state) {
56
+ if (state.closeRequested) {
57
+ return;
58
+ }
59
+ state.closeRequested = true;
60
+ void state.promise
61
+ .then((lifecycle) => lifecycle.close().catch(() => { }))
62
+ .catch(() => { });
63
+ }
64
+ function ensureWechatBridgeLifecycle(input) {
65
+ if (wechatBridgeLifecycleState?.key === input.key) {
66
+ return wechatBridgeLifecycleState.promise;
67
+ }
68
+ const previous = wechatBridgeLifecycleState;
69
+ const promise = input.create();
70
+ const state = {
71
+ key: input.key,
72
+ promise,
73
+ closeRequested: false,
74
+ };
75
+ wechatBridgeLifecycleState = state;
76
+ if (previous) {
77
+ closeWechatBridgeLifecycleState(previous);
78
+ }
79
+ void promise
80
+ .then((lifecycle) => {
81
+ if (wechatBridgeLifecycleState !== state) {
82
+ closeWechatBridgeLifecycleState(state);
83
+ return;
84
+ }
85
+ state.lifecycle = lifecycle;
86
+ })
87
+ .catch((error) => {
88
+ if (wechatBridgeLifecycleState === state) {
89
+ wechatBridgeLifecycleState = undefined;
90
+ }
91
+ console.warn("[plugin-hooks] failed to initialize wechat bridge lifecycle", error);
92
+ });
93
+ return promise;
94
+ }
30
95
  export class InjectCommandHandledError extends Error {
31
96
  constructor() {
32
97
  super("copilot-inject-handled");
@@ -249,6 +314,17 @@ function getFinalSentRequestHeadersRecord(request, init) {
249
314
  headers.delete(INTERNAL_DEBUG_LINK_HEADER);
250
315
  return sanitizeLoggedRequestHeadersRecord(Object.fromEntries(headers.entries()));
251
316
  }
317
+ function hasWechatBridgeClientShape(value) {
318
+ if (typeof value !== "object" || value === null)
319
+ return false;
320
+ const client = value;
321
+ return typeof client.session?.list === "function"
322
+ && typeof client.session?.status === "function"
323
+ && typeof client.session?.todo === "function"
324
+ && typeof client.session?.messages === "function"
325
+ && typeof client.question?.list === "function"
326
+ && typeof client.permission?.list === "function";
327
+ }
252
328
  function sanitizeLoggedRequestHeadersRecord(headers) {
253
329
  const sanitized = { ...headers };
254
330
  if (typeof sanitized.authorization === "string" && sanitized.authorization.length > 0) {
@@ -507,6 +583,26 @@ export function buildPluginHooks(input) {
507
583
  const appendRouteDecisionEventImpl = input.appendRouteDecisionEventImpl ?? appendRouteDecisionEvent;
508
584
  const readRoutingStateImpl = input.readRoutingStateImpl ?? readRoutingState;
509
585
  const triggerBillingCompensation = input.triggerBillingCompensation ?? (async () => { });
586
+ const createWechatBridgeLifecycleImpl = input.createWechatBridgeLifecycleImpl ?? createWechatBridgeLifecycle;
587
+ if (input.serverUrl && hasWechatBridgeClientShape(input.client)) {
588
+ const wechatBridgeClient = input.client;
589
+ const lifecycleKey = buildWechatBridgeLifecycleKey({
590
+ directory: input.directory,
591
+ serverUrl: input.serverUrl,
592
+ project: input.project,
593
+ });
594
+ attachWechatBridgeAutoClose();
595
+ void ensureWechatBridgeLifecycle({
596
+ key: lifecycleKey,
597
+ create: () => createWechatBridgeLifecycleImpl({
598
+ client: wechatBridgeClient,
599
+ project: input.project,
600
+ directory: input.directory,
601
+ serverUrl: input.serverUrl,
602
+ statusCollectionEnabled: true,
603
+ }),
604
+ }).catch(() => { });
605
+ }
510
606
  const loadCandidateAccountLoads = input.loadCandidateAccountLoads ?? (async (ctx) => {
511
607
  const snapshot = await readRoutingStateImpl(routingDirectory);
512
608
  return buildCandidateAccountLoads({
@@ -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)
@@ -370,7 +371,11 @@ export function createCodexMenuAdapter(inputDeps) {
370
371
  if (action.name === "toggle-loop-safety"
371
372
  || action.name === "toggle-loop-safety-provider-scope"
372
373
  || action.name === "toggle-experimental-slash-commands"
373
- || action.name === "toggle-network-retry") {
374
+ || action.name === "toggle-network-retry"
375
+ || action.name === "toggle-wechat-notifications"
376
+ || action.name === "toggle-wechat-question-notify"
377
+ || action.name === "toggle-wechat-permission-notify"
378
+ || action.name === "toggle-wechat-session-error-notify") {
374
379
  await applyCommonSettingsAction({
375
380
  action: { type: action.name },
376
381
  readSettings: readCommonSettings,
@@ -378,6 +383,14 @@ export function createCodexMenuAdapter(inputDeps) {
378
383
  });
379
384
  return false;
380
385
  }
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;
393
+ }
381
394
  return false;
382
395
  },
383
396
  };
@@ -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";
@@ -726,6 +727,10 @@ export function createCopilotMenuAdapter(inputDeps) {
726
727
  || action.name === "toggle-loop-safety-provider-scope"
727
728
  || action.name === "toggle-experimental-slash-commands"
728
729
  || action.name === "toggle-network-retry"
730
+ || action.name === "toggle-wechat-notifications"
731
+ || action.name === "toggle-wechat-question-notify"
732
+ || action.name === "toggle-wechat-permission-notify"
733
+ || action.name === "toggle-wechat-session-error-notify"
729
734
  || action.name === "toggle-synthetic-agent-initiator") {
730
735
  await applyMenuAction({
731
736
  action: { type: action.name },
@@ -736,6 +741,14 @@ export function createCopilotMenuAdapter(inputDeps) {
736
741
  });
737
742
  return false;
738
743
  }
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;
751
+ }
739
752
  if (action.name === "list-models") {
740
753
  const modelID = await select([
741
754
  { label: "Back", value: "" },
@@ -2,5 +2,6 @@ export declare function accountSwitcherConfigDir(): string;
2
2
  export declare function commonSettingsPath(): string;
3
3
  export declare function copilotAccountsPath(): string;
4
4
  export declare function codexAccountsPath(): string;
5
+ export declare function wechatConfigDir(): string;
5
6
  export declare function legacyCopilotStorePath(): string;
6
7
  export declare function legacyCodexStorePath(): string;
@@ -16,6 +16,9 @@ export function copilotAccountsPath() {
16
16
  export function codexAccountsPath() {
17
17
  return path.join(accountSwitcherConfigDir(), "codex-accounts.json");
18
18
  }
19
+ export function wechatConfigDir() {
20
+ return path.join(accountSwitcherConfigDir(), "wechat");
21
+ }
19
22
  export function legacyCopilotStorePath() {
20
23
  return path.join(configBaseDir(), "opencode", "copilot-accounts.json");
21
24
  }
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,6 +71,20 @@ export type MenuAction = {
69
71
  type: "toggle-experimental-slash-commands";
70
72
  } | {
71
73
  type: "toggle-network-retry";
74
+ } | {
75
+ type: "wechat-menu";
76
+ } | {
77
+ type: "wechat-bind";
78
+ } | {
79
+ type: "wechat-rebind";
80
+ } | {
81
+ type: "toggle-wechat-notifications";
82
+ } | {
83
+ type: "toggle-wechat-question-notify";
84
+ } | {
85
+ type: "toggle-wechat-permission-notify";
86
+ } | {
87
+ type: "toggle-wechat-session-error-notify";
72
88
  } | {
73
89
  type: "toggle-synthetic-agent-initiator";
74
90
  } | {
@@ -84,6 +100,42 @@ export type MenuAction = {
84
100
  };
85
101
  export type MenuLanguage = "zh" | "en";
86
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
+ };
87
139
  type MenuCapabilities = {
88
140
  importAuth: boolean;
89
141
  quota: boolean;
@@ -96,6 +148,7 @@ type MenuCapabilities = {
96
148
  experimentalSlashCommands: boolean;
97
149
  networkRetry: boolean;
98
150
  syntheticAgentInitiator: boolean;
151
+ wechatNotificationsMenu: boolean;
99
152
  };
100
153
  export declare function getMenuCopy(language?: MenuLanguage, provider?: MenuProvider): {
101
154
  menuTitle: string;
@@ -130,6 +183,16 @@ export declare function getMenuCopy(language?: MenuLanguage, provider?: MenuProv
130
183
  syntheticInitiatorOn: string;
131
184
  syntheticInitiatorOff: string;
132
185
  syntheticInitiatorHint: string;
186
+ wechatNotificationsHeading: string;
187
+ wechatBind: string;
188
+ wechatNotificationsOn: string;
189
+ wechatNotificationsOff: string;
190
+ wechatQuestionNotifyOn: string;
191
+ wechatQuestionNotifyOff: string;
192
+ wechatPermissionNotifyOn: string;
193
+ wechatPermissionNotifyOff: string;
194
+ wechatSessionErrorNotifyOn: string;
195
+ wechatSessionErrorNotifyOff: string;
133
196
  accountsHeading: string;
134
197
  dangerHeading: string;
135
198
  removeAll: string;
@@ -147,48 +210,24 @@ export declare function buildMenuItems(input: {
147
210
  loopSafetyEnabled: boolean;
148
211
  loopSafetyProviderScope?: "copilot-only" | "all-models";
149
212
  networkRetryEnabled: boolean;
213
+ wechatNotificationsEnabled?: boolean;
214
+ wechatQuestionNotifyEnabled?: boolean;
215
+ wechatPermissionNotifyEnabled?: boolean;
216
+ wechatSessionErrorNotifyEnabled?: boolean;
217
+ wechatPrimaryBinding?: MenuWechatPrimaryBinding;
218
+ wechatOperatorBinding?: MenuWechatOperatorBinding;
150
219
  syntheticAgentInitiatorEnabled?: boolean;
151
220
  experimentalSlashCommandsEnabled?: boolean;
152
221
  capabilities?: Partial<MenuCapabilities>;
153
222
  language?: MenuLanguage;
154
223
  }): MenuItem<MenuAction>[];
155
- export declare function showMenu(accounts: AccountInfo[], input?: {
156
- provider?: MenuProvider;
157
- refresh?: {
158
- enabled: boolean;
159
- minutes: number;
160
- };
161
- lastQuotaRefresh?: number;
162
- modelAccountAssignmentCount?: number;
163
- defaultAccountGroupCount?: number;
164
- loopSafetyEnabled?: boolean;
165
- loopSafetyProviderScope?: "copilot-only" | "all-models";
166
- networkRetryEnabled?: boolean;
167
- syntheticAgentInitiatorEnabled?: boolean;
168
- experimentalSlashCommandsEnabled?: boolean;
169
- capabilities?: Partial<MenuCapabilities>;
170
- language?: MenuLanguage;
171
- }): Promise<MenuAction>;
172
- export declare function showMenuWithDeps(accounts: AccountInfo[], input?: {
173
- provider?: MenuProvider;
174
- refresh?: {
175
- enabled: boolean;
176
- minutes: number;
177
- };
178
- lastQuotaRefresh?: number;
179
- modelAccountAssignmentCount?: number;
180
- defaultAccountGroupCount?: number;
181
- loopSafetyEnabled?: boolean;
182
- loopSafetyProviderScope?: "copilot-only" | "all-models";
183
- networkRetryEnabled?: boolean;
184
- syntheticAgentInitiatorEnabled?: boolean;
185
- experimentalSlashCommandsEnabled?: boolean;
186
- capabilities?: Partial<MenuCapabilities>;
187
- language?: MenuLanguage;
188
- }, deps?: {
224
+ export declare function showMenu(accounts: AccountInfo[], input?: ShowMenuInput): Promise<MenuAction>;
225
+ export declare function showMenuWithDeps(accounts: AccountInfo[], input?: ShowMenuInput, deps?: {
189
226
  select?: typeof select;
190
227
  confirm?: typeof confirm;
191
228
  showAccountActions?: typeof showAccountActions;
229
+ readCommonSettings?: () => Promise<CommonSettingsStore>;
230
+ readOperatorBinding?: () => Promise<OperatorBinding | undefined>;
192
231
  }): Promise<MenuAction>;
193
232
  export declare function buildAccountActionItems(account: AccountInfo, input?: {
194
233
  provider?: MenuProvider;