opencode-copilot-account-switcher 0.14.5 → 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.
@@ -18,10 +18,10 @@ function toErrorMessage(error) {
18
18
  return String(error);
19
19
  }
20
20
  function pickQrTerminal(value) {
21
- return pickFirstNonEmptyString(value?.terminalQr, value?.qrTerminal, value?.qrText, value?.asciiQr);
21
+ return pickFirstNonEmptyString(value?.qrTerminal);
22
22
  }
23
23
  function pickQrUrl(value) {
24
- return pickFirstNonEmptyString(value?.qrDataUrl, value?.qrUrl, value?.url, value?.loginUrl);
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) => {
@@ -57,7 +63,10 @@ export async function runWechatBindFlow(input) {
57
63
  const qrTerminal = pickQrTerminal(started);
58
64
  const qrUrl = pickQrUrl(started);
59
65
  const qrStartMessage = pickFirstNonEmptyString(started?.message, started?.detail, started?.reason);
60
- const sessionKey = pickFirstNonEmptyString(started?.sessionKey, started?.key, started?.accountId);
66
+ const sessionKey = pickFirstNonEmptyString(started?.sessionKey);
67
+ if (!sessionKey) {
68
+ throw new Error("missing sessionKey from qr start");
69
+ }
61
70
  if (qrTerminal) {
62
71
  await writeLine(qrTerminal);
63
72
  }
@@ -78,49 +87,44 @@ export async function runWechatBindFlow(input) {
78
87
  if (waited && typeof waited === "object" && "connected" in waited && waited.connected === false) {
79
88
  throw new Error("qr login did not complete");
80
89
  }
81
- const accountId = pickFirstNonEmptyString(waited?.accountId, helpers.latestAccountState?.accountId, (await helpers.accountHelpers.listAccountIds()).at(-1));
90
+ const accountId = pickFirstNonEmptyString(waited?.accountId);
82
91
  if (!accountId) {
83
92
  throw new Error("missing accountId after qr login");
84
93
  }
85
94
  const boundAt = now();
86
- const userIdFromWait = pickFirstNonEmptyString(waited?.userId, waited?.openid, waited?.uid);
87
95
  const previousOperatorBinding = input.action === "wechat-rebind" ? await loadOperatorBinding() : undefined;
96
+ const userIdFromWait = pickFirstNonEmptyString(waited?.userId);
88
97
  let menuAccount;
89
98
  let boundUserId = "";
90
99
  let shouldRollbackBinding = false;
100
+ let attemptedOperatorBinding;
91
101
  try {
92
- const menuAccountState = accountId
93
- ? {
94
- ...(helpers.latestAccountState ?? {
95
- accountId,
96
- token: "",
97
- baseUrl: "https://ilinkai.weixin.qq.com",
98
- }),
99
- accountId,
100
- ...(userIdFromWait ? { userId: userIdFromWait } : {}),
101
- boundAt,
102
- }
103
- : helpers.latestAccountState;
102
+ const menuAccountState = {
103
+ accountId,
104
+ token: "",
105
+ baseUrl: "https://ilinkai.weixin.qq.com",
106
+ };
104
107
  menuAccount = await buildOpenClawMenuAccount({
105
108
  latestAccountState: menuAccountState,
106
109
  accountHelpers: helpers.accountHelpers,
107
110
  });
108
- const userId = pickFirstNonEmptyString(userIdFromWait, menuAccount?.userId);
111
+ const userId = pickFirstNonEmptyString(menuAccount?.userId, userIdFromWait);
109
112
  if (!userId) {
110
113
  throw new Error("missing userId after qr login");
111
114
  }
112
115
  boundUserId = userId;
113
- const operatorBinding = {
116
+ attemptedOperatorBinding = {
114
117
  wechatAccountId: accountId,
115
118
  userId,
116
119
  boundAt,
117
120
  };
118
- shouldRollbackBinding = true;
119
121
  if (input.action === "wechat-rebind") {
120
- await persistOperatorRebinding(operatorBinding);
122
+ shouldRollbackBinding = true;
123
+ await persistOperatorRebinding(attemptedOperatorBinding);
121
124
  }
122
125
  else {
123
- await persistOperatorBinding(operatorBinding);
126
+ shouldRollbackBinding = true;
127
+ await persistOperatorBinding(attemptedOperatorBinding);
124
128
  }
125
129
  const settings = await input.readCommonSettings();
126
130
  const notifications = settings.wechat?.notifications ?? {
@@ -145,7 +149,15 @@ export async function runWechatBindFlow(input) {
145
149
  }
146
150
  catch (error) {
147
151
  if (shouldRollbackBinding) {
148
- 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
+ }
149
161
  }
150
162
  throw error;
151
163
  }
@@ -0,0 +1,29 @@
1
+ export type WeixinAccountHelpers = {
2
+ listAccountIds: () => Promise<string[]>;
3
+ resolveAccount: (accountId: string) => Promise<{
4
+ accountId: string;
5
+ enabled: boolean;
6
+ configured: boolean;
7
+ name?: string;
8
+ userId?: string;
9
+ }>;
10
+ describeAccount: (accountIdOrInput: string | {
11
+ accountId: string;
12
+ }) => Promise<{
13
+ accountId: string;
14
+ enabled: boolean;
15
+ configured: boolean;
16
+ name?: string;
17
+ userId?: string;
18
+ }>;
19
+ };
20
+ type OpenClawAccountSourceHelpers = {
21
+ listAccountIds: () => string[] | Promise<string[]>;
22
+ loadAccount: (accountId: string) => unknown | Promise<unknown>;
23
+ resolveAccount: (accountId: string) => unknown | Promise<unknown>;
24
+ };
25
+ export declare function createOpenClawAccountHelpers(input: OpenClawAccountSourceHelpers): WeixinAccountHelpers;
26
+ export declare function loadOpenClawAccountHelpers(options?: {
27
+ accountsModulePath?: string;
28
+ }): Promise<WeixinAccountHelpers>;
29
+ export {};
@@ -0,0 +1,71 @@
1
+ import { createRequire } from "node:module";
2
+ import { createJiti } from "jiti";
3
+ const OPENCLAW_WEIXIN_ACCOUNTS_MODULE = "@tencent-weixin/openclaw-weixin/src/auth/accounts.ts";
4
+ let accountJitiLoader = null;
5
+ function getAccountJiti() {
6
+ if (accountJitiLoader) {
7
+ return accountJitiLoader;
8
+ }
9
+ accountJitiLoader = createJiti(import.meta.url, {
10
+ interopDefault: true,
11
+ extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs", ".json"],
12
+ });
13
+ return accountJitiLoader;
14
+ }
15
+ function asObject(value) {
16
+ return value && typeof value === "object" ? value : {};
17
+ }
18
+ function deriveEnabled(resolved) {
19
+ return resolved.enabled === true;
20
+ }
21
+ function isConfigured(resolved) {
22
+ return resolved.configured === true;
23
+ }
24
+ function toAccountId(input) {
25
+ return typeof input === "string" ? input : input.accountId;
26
+ }
27
+ export function createOpenClawAccountHelpers(input) {
28
+ const resolveStableAccount = async (accountId) => {
29
+ const resolved = asObject(await input.resolveAccount(accountId));
30
+ const stored = asObject(await input.loadAccount(accountId));
31
+ return {
32
+ accountId,
33
+ enabled: deriveEnabled(resolved),
34
+ configured: isConfigured(resolved),
35
+ name: typeof resolved.name === "string" ? resolved.name : undefined,
36
+ userId: typeof stored.userId === "string" ? stored.userId : undefined,
37
+ };
38
+ };
39
+ return {
40
+ async listAccountIds() {
41
+ const ids = await input.listAccountIds();
42
+ return Array.isArray(ids) ? ids.filter((it) => typeof it === "string" && it.length > 0) : [];
43
+ },
44
+ async resolveAccount(accountId) {
45
+ return resolveStableAccount(accountId);
46
+ },
47
+ async describeAccount(accountIdOrInput) {
48
+ return resolveStableAccount(toAccountId(accountIdOrInput));
49
+ },
50
+ };
51
+ }
52
+ export async function loadOpenClawAccountHelpers(options = {}) {
53
+ const require = createRequire(import.meta.url);
54
+ const accountsModulePath = require.resolve(options.accountsModulePath ?? OPENCLAW_WEIXIN_ACCOUNTS_MODULE);
55
+ const accountsModule = getAccountJiti()(accountsModulePath);
56
+ if (typeof accountsModule.listIndexedWeixinAccountIds !== "function" || typeof accountsModule.loadWeixinAccount !== "function") {
57
+ throw new Error("[wechat-compat] account source helper unavailable");
58
+ }
59
+ return createOpenClawAccountHelpers({
60
+ listAccountIds: () => accountsModule.listIndexedWeixinAccountIds(),
61
+ loadAccount: (accountId) => accountsModule.loadWeixinAccount(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
+ },
70
+ });
71
+ }
@@ -102,6 +102,7 @@ export type RunGuidedSmokeOptions = {
102
102
  publicHelpersOptions?: OpenClawWeixinPublicHelpersLoaderOptions;
103
103
  slashCaptureWaitTimeoutMs?: number;
104
104
  slashCapturePollIntervalMs?: number;
105
+ writeLine?: (line: string) => Promise<void> | void;
105
106
  };
106
107
  type PreflightData = {
107
108
  runId: string;
@@ -43,8 +43,11 @@ const DEFAULT_KEY_FIELDS_CHECK = {
43
43
  const DEFAULT_SLASH_CAPTURE_WAIT_TIMEOUT_MS = 180_000;
44
44
  const DEFAULT_SLASH_CAPTURE_POLL_INTERVAL_MS = 2_000;
45
45
  const DEFAULT_PUBLIC_GET_UPDATES_LONG_POLL_TIMEOUT_MS = 35_000;
46
- function printGuidedPrompt(message) {
47
- process.stdout.write(`${message}\n`);
46
+ async function writeLineDefault(line) {
47
+ process.stdout.write(`${line}\n`);
48
+ }
49
+ async function printGuidedPrompt(message, writeLine) {
50
+ await writeLine(message);
48
51
  }
49
52
  function createRunId() {
50
53
  return new Date().toISOString().replace(/[:.]/g, "-");
@@ -390,15 +393,18 @@ async function runQrLoginDefault(input) {
390
393
  timeoutMs: input.waitTimeoutMs,
391
394
  verbose: false,
392
395
  }));
393
- const qrTerminal = pickFirstString(startResult, ["terminalQr", "qrTerminal", "qrText", "asciiQr"]);
394
- const qrUrl = pickFirstString(startResult, ["qrDataUrl", "qrUrl", "url", "loginUrl"]);
395
- const sessionKey = pickFirstString(startResult, ["sessionKey", "accountId"]);
396
+ const qrTerminal = pickFirstString(startResult, ["qrTerminal"]);
397
+ const qrUrl = pickFirstString(startResult, ["qrDataUrl"]);
398
+ const sessionKey = pickFirstString(startResult, ["sessionKey"]);
396
399
  const qrStartMessage = pickFirstString(startResult, ["message", "detail", "reason"]);
400
+ if (!sessionKey) {
401
+ throw new Error("missing sessionKey from qr start");
402
+ }
397
403
  if (qrTerminal) {
398
- process.stdout.write(`${qrTerminal}\n`);
404
+ await input.writeLine(qrTerminal);
399
405
  }
400
406
  else if (qrUrl) {
401
- process.stdout.write(`QR URL fallback: ${qrUrl}\n`);
407
+ await input.writeLine(`QR URL fallback: ${qrUrl}`);
402
408
  }
403
409
  else {
404
410
  throw new Error(qrStartMessage || "invalid qr login result: missing qr code or qr url");
@@ -625,7 +631,7 @@ async function runDefaultNonSlashVerification(input) {
625
631
  };
626
632
  }
627
633
  export const runDefaultNonSlashVerificationForTest = runDefaultNonSlashVerification;
628
- async function runSlashSampling(run, captureSlashInbound) {
634
+ async function runSlashSampling(run, captureSlashInbound, writeLine) {
629
635
  const harness = createOpenClawSmokeHarness({ mode: "real-account" });
630
636
  for (const step of SLASH_SAMPLE_STEPS) {
631
637
  const inbound = await captureSlashInbound(step.command);
@@ -648,13 +654,13 @@ async function runSlashSampling(run, captureSlashInbound) {
648
654
  };
649
655
  }
650
656
  if (step.command === "status") {
651
- printGuidedPrompt("已收到 `/status`,下一步请发送 `/reply smoke`");
657
+ await printGuidedPrompt("已收到 `/status`,下一步请发送 `/reply smoke`", writeLine);
652
658
  }
653
659
  else if (step.command === "reply") {
654
- printGuidedPrompt("已收到 `/reply smoke`,下一步请发送 `/allow once`");
660
+ await printGuidedPrompt("已收到 `/reply smoke`,下一步请发送 `/allow once`", writeLine);
655
661
  }
656
662
  else {
657
- printGuidedPrompt("已收到 `/allow once`,下一步开始发送 10 条普通文本");
663
+ await printGuidedPrompt("已收到 `/allow once`,下一步开始发送 10 条普通文本", writeLine);
658
664
  }
659
665
  }
660
666
  return { ok: true };
@@ -689,23 +695,13 @@ function normalizeQrLoginResult(result) {
689
695
  return null;
690
696
  }
691
697
  if (candidate.status === "success") {
692
- if (typeof candidate.connected === "boolean" && candidate.connected !== true) {
693
- return {
694
- status: "timeout",
695
- qrPrinted: candidate.qrPrinted === true,
696
- qrUrl: typeof candidate.qrDataUrl === "string" && candidate.qrDataUrl.trim().length > 0
697
- ? candidate.qrDataUrl
698
- : typeof candidate.qrUrl === "string"
699
- ? candidate.qrUrl
700
- : undefined,
701
- };
698
+ if (candidate.connected !== true) {
699
+ return null;
702
700
  }
703
701
  const hasPrintedQr = candidate.qrPrinted === true;
704
702
  const qrUrl = typeof candidate.qrDataUrl === "string" && candidate.qrDataUrl.trim().length > 0
705
703
  ? candidate.qrDataUrl
706
- : typeof candidate.qrUrl === "string" && candidate.qrUrl.trim().length > 0
707
- ? candidate.qrUrl
708
- : undefined;
704
+ : undefined;
709
705
  const hasQrUrl = Boolean(qrUrl);
710
706
  if (!hasPrintedQr && !hasQrUrl) {
711
707
  return null;
@@ -713,9 +709,7 @@ function normalizeQrLoginResult(result) {
713
709
  }
714
710
  const normalizedQrUrl = typeof candidate.qrDataUrl === "string" && candidate.qrDataUrl.trim().length > 0
715
711
  ? candidate.qrDataUrl
716
- : typeof candidate.qrUrl === "string"
717
- ? candidate.qrUrl
718
- : undefined;
712
+ : undefined;
719
713
  return {
720
714
  status: candidate.status,
721
715
  connected: candidate.connected === true ? true : undefined,
@@ -819,6 +813,7 @@ async function completeWithNoGoFinalEvidence(run, input, goNoGoDocPath) {
819
813
  export async function runGuidedSmoke(options = {}) {
820
814
  const run = createGuidedSmokeRun(options);
821
815
  const waitTimeoutMs = options.qrWaitTimeoutMs ?? DEFAULT_QR_WAIT_TIMEOUT_MS;
816
+ const writeLine = options.writeLine ?? writeLineDefault;
822
817
  const slashCaptureState = {};
823
818
  const loadOpenClawPublicHelpers = options.loadOpenClawWeixinPublicHelpers ?? loadOpenClawWeixinPublicHelpers;
824
819
  let cachedPublicHelpers = null;
@@ -839,7 +834,11 @@ export async function runGuidedSmoke(options = {}) {
839
834
  const getDependencyVersions = options.getDependencyVersions ?? resolveDependencyVersionsFromPackageJson;
840
835
  const runQrLogin = options.runQrLogin ?? (async (input) => {
841
836
  const helpers = await getPublicHelpers();
842
- return runQrLoginDefault({ ...input, qrGateway: helpers.qrGateway });
837
+ return runQrLoginDefault({
838
+ ...input,
839
+ qrGateway: helpers.qrGateway,
840
+ writeLine,
841
+ });
843
842
  });
844
843
  const captureSlashInbound = options.captureSlashInbound ?? ((command) => captureSlashInboundDefault(command, {
845
844
  state: slashCaptureState,
@@ -957,7 +956,7 @@ export async function runGuidedSmoke(options = {}) {
957
956
  }, goNoGoDocPath);
958
957
  }
959
958
  await writeLoginSuccessEvidence(run, "success", waitTimeoutMs);
960
- printGuidedPrompt("二维码登录成功,下一步请发送 `/status`");
959
+ await printGuidedPrompt("二维码登录成功,下一步请发送 `/status`", writeLine);
961
960
  }
962
961
  catch (error) {
963
962
  const detail = error instanceof Error ? error.message : String(error);
@@ -971,7 +970,7 @@ export async function runGuidedSmoke(options = {}) {
971
970
  }
972
971
  let slashSampling;
973
972
  try {
974
- slashSampling = await runSlashSampling(run, captureSlashInbound);
973
+ slashSampling = await runSlashSampling(run, captureSlashInbound, writeLine);
975
974
  }
976
975
  catch (error) {
977
976
  return failWithFinalEvidence(run, {
@@ -1053,7 +1052,7 @@ export async function runGuidedSmoke(options = {}) {
1053
1052
  keyFields: nonSlashVerification.keyFieldsCheck,
1054
1053
  }, goNoGoDocPath);
1055
1054
  }
1056
- printGuidedPrompt(`普通文本验证进度:${index + 1}/${attempts.length}`);
1055
+ await printGuidedPrompt(`普通文本验证进度:${index + 1}/${attempts.length}`, writeLine);
1057
1056
  }
1058
1057
  const nonSlashPassed = nonSlashVerification.passed === 10 && nonSlashVerification.total === 10;
1059
1058
  if (!nonSlashPassed) {
@@ -0,0 +1,33 @@
1
+ type CompatHostApi = {
2
+ runtime?: {
3
+ channelRuntime?: unknown;
4
+ gateway?: {
5
+ startAccount?: unknown;
6
+ };
7
+ };
8
+ registerChannel?: (input: unknown) => void;
9
+ registerCli?: (handler: unknown, options?: unknown) => void;
10
+ };
11
+ type OpenClawWeixinPlugin = {
12
+ id?: string;
13
+ register(api: CompatHostApi): void;
14
+ };
15
+ export type OpenClawWeixinPublicEntry = {
16
+ packageJsonPath: string;
17
+ packageRoot: string;
18
+ extensions: string[];
19
+ entryRelativePath: string;
20
+ entryAbsolutePath: string;
21
+ };
22
+ export declare function resolveOpenClawWeixinPublicEntry(): Promise<OpenClawWeixinPublicEntry>;
23
+ export declare function loadOpenClawWeixinDefaultExport(): Promise<OpenClawWeixinPlugin>;
24
+ export declare function loadRegisteredWeixinPluginPayloads(): Promise<Array<{
25
+ plugin?: unknown;
26
+ }>>;
27
+ export declare function loadRegisteredWeixinPluginContext(): Promise<{
28
+ pluginId: string;
29
+ payloads: Array<{
30
+ plugin?: unknown;
31
+ }>;
32
+ }>;
33
+ export {};
@@ -0,0 +1,73 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ import { createJiti } from "jiti";
5
+ let publicJitiLoader = null;
6
+ function requireField(condition, message) {
7
+ if (!condition) {
8
+ throw new Error(`[wechat-compat] ${message}`);
9
+ }
10
+ }
11
+ function getPublicJiti() {
12
+ if (publicJitiLoader) {
13
+ return publicJitiLoader;
14
+ }
15
+ publicJitiLoader = createJiti(import.meta.url, {
16
+ interopDefault: true,
17
+ extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs", ".json"],
18
+ });
19
+ return publicJitiLoader;
20
+ }
21
+ export async function resolveOpenClawWeixinPublicEntry() {
22
+ const require = createRequire(import.meta.url);
23
+ const packageName = "@tencent-weixin/openclaw-weixin";
24
+ const packageJsonPath = require.resolve(`${packageName}/package.json`);
25
+ const packageJsonRaw = await readFile(packageJsonPath, "utf8");
26
+ const packageJson = JSON.parse(packageJsonRaw);
27
+ const extensions = Array.isArray(packageJson.openclaw?.extensions)
28
+ ? packageJson.openclaw?.extensions.filter((it) => typeof it === "string")
29
+ : [];
30
+ requireField(extensions.length > 0, `${packageName} openclaw.extensions[0] is required`);
31
+ const entryRelativePath = extensions[0];
32
+ requireField(Boolean(entryRelativePath?.startsWith("./")), `${packageName} openclaw.extensions[0] must start with ./`);
33
+ const packageRoot = path.dirname(packageJsonPath);
34
+ const entryAbsolutePath = path.resolve(packageRoot, entryRelativePath);
35
+ return {
36
+ packageJsonPath,
37
+ packageRoot,
38
+ extensions,
39
+ entryRelativePath,
40
+ entryAbsolutePath,
41
+ };
42
+ }
43
+ export async function loadOpenClawWeixinDefaultExport() {
44
+ const entry = await resolveOpenClawWeixinPublicEntry();
45
+ const moduleNamespace = getPublicJiti()(entry.entryAbsolutePath);
46
+ const plugin = moduleNamespace.default;
47
+ if (!plugin || typeof plugin !== "object" || typeof plugin.register !== "function") {
48
+ throw new Error("[wechat-compat] @tencent-weixin/openclaw-weixin public entry default export is missing register(api)");
49
+ }
50
+ return plugin;
51
+ }
52
+ export async function loadRegisteredWeixinPluginPayloads() {
53
+ const context = await loadRegisteredWeixinPluginContext();
54
+ return context.payloads;
55
+ }
56
+ export async function loadRegisteredWeixinPluginContext() {
57
+ const payloads = [];
58
+ const plugin = await loadOpenClawWeixinDefaultExport();
59
+ plugin.register({
60
+ runtime: {
61
+ channelRuntime: { mode: "guided-smoke" },
62
+ gateway: { startAccount: { source: "guided-smoke" } },
63
+ },
64
+ registerChannel(payload) {
65
+ payloads.push(payload);
66
+ },
67
+ registerCli() { },
68
+ });
69
+ return {
70
+ pluginId: typeof plugin.id === "string" && plugin.id.trim().length > 0 ? plugin.id : "wechat-openclaw-weixin",
71
+ payloads,
72
+ };
73
+ }
@@ -1,63 +1,14 @@
1
+ import { loadOpenClawAccountHelpers, type WeixinAccountHelpers } from "./openclaw-account-helpers.js";
2
+ import { loadRegisteredWeixinPluginContext, loadRegisteredWeixinPluginPayloads, resolveOpenClawWeixinPublicEntry, type OpenClawWeixinPublicEntry } from "./openclaw-public-entry.js";
3
+ import { type WeixinQrGateway } from "./openclaw-qr-gateway.js";
4
+ import { loadOpenClawUpdatesAndSendHelpers, type PublicWeixinMessage, type PublicWeixinSendMessage } from "./openclaw-updates-send.js";
5
+ import { loadLatestWeixinAccountState, loadOpenClawSyncBufHelper, type PublicWeixinPersistGetUpdatesBuf } from "./openclaw-sync-buf.js";
1
6
  export declare const OPENCLAW_WEIXIN_JITI_SRC_HELPER_MODULES: {
2
7
  readonly stateDir: "@tencent-weixin/openclaw-weixin/src/storage/state-dir.ts";
3
8
  readonly syncBuf: "@tencent-weixin/openclaw-weixin/src/storage/sync-buf.ts";
4
9
  readonly getUpdates: "@tencent-weixin/openclaw-weixin/src/api/api.ts";
5
10
  readonly sendMessageWeixin: "@tencent-weixin/openclaw-weixin/src/messaging/send.ts";
6
11
  };
7
- type WeixinQrGateway = {
8
- loginWithQrStart: (input?: unknown) => unknown;
9
- loginWithQrWait: (input?: unknown) => unknown;
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
- };
18
- export type OpenClawWeixinPublicEntry = {
19
- packageJsonPath: string;
20
- packageRoot: string;
21
- extensions: string[];
22
- entryRelativePath: string;
23
- entryAbsolutePath: string;
24
- };
25
- declare function resolveOpenClawWeixinPublicEntry(): Promise<OpenClawWeixinPublicEntry>;
26
- declare function loadPublicWeixinAccountHelpers(): Promise<WeixinAccountHelpers>;
27
- declare function loadLatestWeixinAccountState(): Promise<{
28
- accountId: string;
29
- token: string;
30
- baseUrl: string;
31
- getUpdatesBuf?: string;
32
- } | null>;
33
- type PublicWeixinMessageItem = {
34
- type?: number;
35
- text_item?: {
36
- text?: string;
37
- };
38
- };
39
- export type PublicWeixinMessage = {
40
- message_id?: number;
41
- from_user_id?: string;
42
- context_token?: string;
43
- create_time_ms?: number;
44
- item_list?: PublicWeixinMessageItem[];
45
- };
46
- export type PublicWeixinSendMessage = (params: {
47
- to: string;
48
- text: string;
49
- opts: {
50
- baseUrl: string;
51
- token: string;
52
- contextToken?: string;
53
- };
54
- }) => Promise<{
55
- messageId: string;
56
- }>;
57
- export type PublicWeixinPersistGetUpdatesBuf = (params: {
58
- accountId: string;
59
- getUpdatesBuf: string;
60
- }) => Promise<void>;
61
12
  export type OpenClawWeixinPublicHelpers = {
62
13
  entry: OpenClawWeixinPublicEntry;
63
14
  pluginId: string;
@@ -83,29 +34,37 @@ export type OpenClawWeixinPublicHelpers = {
83
34
  };
84
35
  type OpenClawWeixinPublicHelpersLoaders = {
85
36
  resolveOpenClawWeixinPublicEntry?: typeof resolveOpenClawWeixinPublicEntry;
37
+ loadRegisteredWeixinPluginContext?: typeof loadRegisteredWeixinPluginContext;
38
+ loadRegisteredWeixinPluginPayloads?: typeof loadRegisteredWeixinPluginPayloads;
39
+ loadOpenClawQrGateway?: (payloads: Array<{
40
+ plugin?: unknown;
41
+ }>) => Promise<{
42
+ gateway: WeixinQrGateway;
43
+ pluginId: string;
44
+ }>;
86
45
  loadPublicWeixinQrGateway?: () => Promise<{
87
46
  gateway: WeixinQrGateway;
88
47
  pluginId?: string;
89
48
  }>;
90
49
  loadLatestWeixinAccountState?: typeof loadLatestWeixinAccountState;
91
- loadPublicWeixinAccountHelpers?: typeof loadPublicWeixinAccountHelpers;
92
- loadPublicWeixinHelpers?: typeof loadPublicWeixinHelpers;
93
- loadPublicWeixinSendHelper?: typeof loadPublicWeixinSendHelper;
94
- };
95
- declare function loadPublicWeixinHelpers(): Promise<{
96
- getUpdates: (params: {
97
- baseUrl: string;
98
- token?: string;
99
- get_updates_buf?: string;
100
- timeoutMs?: number;
101
- }) => Promise<{
102
- msgs?: PublicWeixinMessage[];
103
- get_updates_buf?: string;
50
+ loadOpenClawAccountHelpers?: typeof loadOpenClawAccountHelpers;
51
+ loadOpenClawUpdatesAndSendHelpers?: typeof loadOpenClawUpdatesAndSendHelpers;
52
+ loadOpenClawSyncBufHelper?: typeof loadOpenClawSyncBufHelper;
53
+ loadPublicWeixinHelpers?: () => Promise<{
54
+ getUpdates: (params: {
55
+ baseUrl: string;
56
+ token?: string;
57
+ get_updates_buf?: string;
58
+ timeoutMs?: number;
59
+ }) => Promise<{
60
+ msgs?: PublicWeixinMessage[];
61
+ get_updates_buf?: string;
62
+ }>;
104
63
  }>;
105
- }>;
106
- declare function loadPublicWeixinSendHelper(): Promise<{
107
- sendMessageWeixin: PublicWeixinSendMessage;
108
- }>;
64
+ loadPublicWeixinSendHelper?: () => Promise<{
65
+ sendMessageWeixin: PublicWeixinSendMessage;
66
+ }>;
67
+ };
109
68
  export declare function loadOpenClawWeixinPublicHelpers(loaders?: OpenClawWeixinPublicHelpersLoaders): Promise<OpenClawWeixinPublicHelpers>;
110
69
  export type OpenClawWeixinPublicHelpersLoaderOptions = OpenClawWeixinPublicHelpersLoaders;
111
- export {};
70
+ export type { PublicWeixinMessage, PublicWeixinSendMessage, PublicWeixinPersistGetUpdatesBuf };
@@ -1,244 +1,50 @@
1
- import { readFile } from "node:fs/promises";
2
- import { createRequire } from "node:module";
3
- import path from "node:path";
4
- import { createJiti } from "jiti";
1
+ import { loadOpenClawAccountHelpers } from "./openclaw-account-helpers.js";
2
+ import { loadRegisteredWeixinPluginContext, resolveOpenClawWeixinPublicEntry, } from "./openclaw-public-entry.js";
3
+ import { loadOpenClawQrGateway } from "./openclaw-qr-gateway.js";
4
+ import { loadOpenClawUpdatesAndSendHelpers, } from "./openclaw-updates-send.js";
5
+ import { loadLatestWeixinAccountState, loadOpenClawSyncBufHelper, } from "./openclaw-sync-buf.js";
5
6
  export const OPENCLAW_WEIXIN_JITI_SRC_HELPER_MODULES = {
6
7
  stateDir: "@tencent-weixin/openclaw-weixin/src/storage/state-dir.ts",
7
8
  syncBuf: "@tencent-weixin/openclaw-weixin/src/storage/sync-buf.ts",
8
9
  getUpdates: "@tencent-weixin/openclaw-weixin/src/api/api.ts",
9
10
  sendMessageWeixin: "@tencent-weixin/openclaw-weixin/src/messaging/send.ts",
10
11
  };
11
- let publicJitiLoader = null;
12
- function requireField(condition, message) {
13
- if (!condition) {
14
- throw new Error(`[wechat-compat] ${message}`);
15
- }
16
- }
17
- function getPublicJiti() {
18
- if (publicJitiLoader) {
19
- return publicJitiLoader;
20
- }
21
- publicJitiLoader = createJiti(import.meta.url, {
22
- interopDefault: true,
23
- extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs", ".json"],
24
- });
25
- return publicJitiLoader;
26
- }
27
- function hasQrLoginMethods(value) {
28
- if (!value || typeof value !== "object") {
29
- return false;
30
- }
31
- const candidate = value;
32
- return typeof candidate.loginWithQrStart === "function" && typeof candidate.loginWithQrWait === "function";
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
- }
43
- async function resolveOpenClawWeixinPublicEntry() {
44
- const require = createRequire(import.meta.url);
45
- const packageName = "@tencent-weixin/openclaw-weixin";
46
- const packageJsonPath = require.resolve(`${packageName}/package.json`);
47
- const packageJsonRaw = await readFile(packageJsonPath, "utf8");
48
- const packageJson = JSON.parse(packageJsonRaw);
49
- const extensions = Array.isArray(packageJson.openclaw?.extensions)
50
- ? packageJson.openclaw?.extensions.filter((it) => typeof it === "string")
51
- : [];
52
- requireField(extensions.length > 0, `${packageName} openclaw.extensions[0] is required`);
53
- const entryRelativePath = extensions[0];
54
- requireField(Boolean(entryRelativePath?.startsWith("./")), `${packageName} openclaw.extensions[0] must start with ./`);
55
- const packageRoot = path.dirname(packageJsonPath);
56
- const entryAbsolutePath = path.resolve(packageRoot, entryRelativePath);
57
- return {
58
- packageJsonPath,
59
- packageRoot,
60
- extensions,
61
- entryRelativePath,
62
- entryAbsolutePath,
63
- };
64
- }
65
- async function loadOpenClawWeixinDefaultExport() {
66
- const entry = await resolveOpenClawWeixinPublicEntry();
67
- const moduleNamespace = getPublicJiti()(entry.entryAbsolutePath);
68
- const plugin = moduleNamespace.default;
69
- if (!plugin || typeof plugin !== "object" || typeof plugin.register !== "function") {
70
- throw new Error("[wechat-compat] @tencent-weixin/openclaw-weixin public entry default export is missing register(api)");
71
- }
72
- return plugin;
73
- }
74
- async function loadPublicWeixinQrGateway() {
75
- const registeredPayloads = [];
76
- const compatHostApi = {
77
- runtime: {
78
- channelRuntime: {
79
- mode: "guided-smoke",
80
- },
81
- gateway: {
82
- startAccount: {
83
- source: "guided-smoke",
84
- },
85
- },
86
- },
87
- registerChannel(payload) {
88
- registeredPayloads.push(payload);
89
- },
90
- registerCli() { },
91
- };
92
- const plugin = await loadOpenClawWeixinDefaultExport();
93
- plugin.register(compatHostApi);
94
- for (const payload of registeredPayloads) {
95
- const payloadPlugin = payload?.plugin;
96
- const gateway = payloadPlugin && typeof payloadPlugin === "object" ? payloadPlugin.gateway : null;
97
- if (hasQrLoginMethods(gateway)) {
98
- return { gateway, pluginId: plugin.id ?? "unknown" };
99
- }
100
- if (hasQrLoginMethods(payloadPlugin)) {
101
- return { gateway: payloadPlugin, pluginId: plugin.id ?? "unknown" };
102
- }
103
- }
104
- throw new Error("registerChannel did not expose weixin gateway loginWithQrStart/loginWithQrWait");
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
- }
145
- async function loadLatestWeixinAccountState() {
146
- const require = createRequire(import.meta.url);
147
- const stateDirModulePath = require.resolve(OPENCLAW_WEIXIN_JITI_SRC_HELPER_MODULES.stateDir);
148
- const stateDirModule = getPublicJiti()(stateDirModulePath);
149
- const stateDir = stateDirModule.resolveStateDir?.();
150
- if (!stateDir) {
151
- return null;
152
- }
153
- const accountsIndexPath = path.join(stateDir, "openclaw-weixin", "accounts.json");
154
- let accountIds = [];
155
- try {
156
- const raw = await readFile(accountsIndexPath, "utf8");
157
- const parsed = JSON.parse(raw);
158
- if (Array.isArray(parsed)) {
159
- accountIds = parsed.filter((item) => typeof item === "string" && item.trim().length > 0);
160
- }
161
- }
162
- catch {
163
- return null;
164
- }
165
- const accountId = accountIds.at(-1);
166
- if (!accountId) {
167
- return null;
168
- }
169
- try {
170
- const accountFilePath = path.join(stateDir, "openclaw-weixin", "accounts", `${accountId}.json`);
171
- const accountRaw = await readFile(accountFilePath, "utf8");
172
- const account = JSON.parse(accountRaw);
173
- const syncBufModulePath = require.resolve(OPENCLAW_WEIXIN_JITI_SRC_HELPER_MODULES.syncBuf);
174
- const syncBufModule = getPublicJiti()(syncBufModulePath);
175
- if (typeof account.token !== "string" || account.token.trim().length === 0) {
176
- return null;
177
- }
178
- const syncBufFilePath = syncBufModule.getSyncBufFilePath?.(accountId);
179
- const persistedGetUpdatesBuf = syncBufFilePath ? syncBufModule.loadGetUpdatesBuf?.(syncBufFilePath) : undefined;
180
- return {
181
- accountId,
182
- token: account.token,
183
- baseUrl: typeof account.baseUrl === "string" && account.baseUrl.trim().length > 0 ? account.baseUrl : "https://ilinkai.weixin.qq.com",
184
- getUpdatesBuf: typeof persistedGetUpdatesBuf === "string" ? persistedGetUpdatesBuf : undefined,
185
- };
186
- }
187
- catch {
188
- return null;
189
- }
190
- }
191
12
  function missingHelperError(helperName) {
192
13
  return new Error(`[wechat-compat] required helper missing: ${helperName}`);
193
14
  }
194
- async function loadPublicWeixinHelpers() {
195
- const require = createRequire(import.meta.url);
196
- const getUpdatesModulePath = require.resolve(OPENCLAW_WEIXIN_JITI_SRC_HELPER_MODULES.getUpdates);
197
- const getUpdatesModule = getPublicJiti()(getUpdatesModulePath);
198
- if (typeof getUpdatesModule.getUpdates !== "function") {
199
- throw new Error("public getUpdates helper unavailable");
200
- }
201
- return {
202
- getUpdates: getUpdatesModule.getUpdates,
203
- };
204
- }
205
- async function loadPublicWeixinSendHelper() {
206
- const require = createRequire(import.meta.url);
207
- const sendModulePath = require.resolve(OPENCLAW_WEIXIN_JITI_SRC_HELPER_MODULES.sendMessageWeixin);
208
- const sendModule = getPublicJiti()(sendModulePath);
209
- if (typeof sendModule.sendMessageWeixin !== "function") {
210
- throw new Error("public sendMessageWeixin helper unavailable");
211
- }
212
- return {
213
- sendMessageWeixin: sendModule.sendMessageWeixin,
214
- };
215
- }
216
- async function loadPublicWeixinSyncBufHelpers() {
217
- const require = createRequire(import.meta.url);
218
- const syncBufModulePath = require.resolve(OPENCLAW_WEIXIN_JITI_SRC_HELPER_MODULES.syncBuf);
219
- const syncBufModule = getPublicJiti()(syncBufModulePath);
220
- if (typeof syncBufModule.getSyncBufFilePath !== "function" || typeof syncBufModule.saveGetUpdatesBuf !== "function") {
221
- return {};
222
- }
223
- return {
224
- persistGetUpdatesBuf: async ({ accountId, getUpdatesBuf }) => {
225
- const filePath = syncBufModule.getSyncBufFilePath(accountId);
226
- syncBufModule.saveGetUpdatesBuf(filePath, getUpdatesBuf);
227
- },
228
- };
229
- }
230
15
  export async function loadOpenClawWeixinPublicHelpers(loaders = {}) {
231
16
  const entry = await (loaders.resolveOpenClawWeixinPublicEntry ?? resolveOpenClawWeixinPublicEntry)();
232
- const qrGatewayResult = await (loaders.loadPublicWeixinQrGateway ?? loadPublicWeixinQrGateway)();
233
- const accountHelpers = await (loaders.loadPublicWeixinAccountHelpers ?? loadPublicWeixinAccountHelpers)();
234
- const latestAccountState = await (loaders.loadLatestWeixinAccountState ?? loadLatestWeixinAccountState)();
235
- const publicHelpers = await (loaders.loadPublicWeixinHelpers ?? loadPublicWeixinHelpers)();
236
- const sendHelper = await (loaders.loadPublicWeixinSendHelper ?? loadPublicWeixinSendHelper)();
237
- const syncBufHelpers = await loadPublicWeixinSyncBufHelpers();
17
+ const qrGatewayResult = loaders.loadPublicWeixinQrGateway
18
+ ? await loaders.loadPublicWeixinQrGateway()
19
+ : await (async () => {
20
+ if (loaders.loadRegisteredWeixinPluginContext) {
21
+ const context = await loaders.loadRegisteredWeixinPluginContext();
22
+ return (loaders.loadOpenClawQrGateway ?? loadOpenClawQrGateway)(context.payloads, { pluginId: context.pluginId });
23
+ }
24
+ const context = await loadRegisteredWeixinPluginContext();
25
+ return (loaders.loadOpenClawQrGateway ?? loadOpenClawQrGateway)(context.payloads, { pluginId: context.pluginId });
26
+ })();
27
+ const accountHelpers = await (loaders.loadOpenClawAccountHelpers ?? loadOpenClawAccountHelpers)();
28
+ const latestAccountState = await (loaders.loadLatestWeixinAccountState ?? loadLatestWeixinAccountState)({
29
+ stateDirModulePath: OPENCLAW_WEIXIN_JITI_SRC_HELPER_MODULES.stateDir,
30
+ syncBufModulePath: OPENCLAW_WEIXIN_JITI_SRC_HELPER_MODULES.syncBuf,
31
+ });
32
+ const updatesSend = loaders.loadOpenClawUpdatesAndSendHelpers
33
+ ? await loaders.loadOpenClawUpdatesAndSendHelpers()
34
+ : await (async () => {
35
+ const defaults = await loadOpenClawUpdatesAndSendHelpers();
36
+ const maybeUpdates = loaders.loadPublicWeixinHelpers ? await loaders.loadPublicWeixinHelpers() : undefined;
37
+ const maybeSend = loaders.loadPublicWeixinSendHelper ? await loaders.loadPublicWeixinSendHelper() : undefined;
38
+ return {
39
+ getUpdates: loaders.loadPublicWeixinHelpers ? maybeUpdates?.getUpdates : defaults.getUpdates,
40
+ sendMessageWeixin: loaders.loadPublicWeixinSendHelper ? maybeSend?.sendMessageWeixin : defaults.sendMessageWeixin,
41
+ };
42
+ })();
43
+ const syncBufHelpers = await (loaders.loadOpenClawSyncBufHelper ?? loadOpenClawSyncBufHelper)();
238
44
  if (typeof qrGatewayResult?.gateway?.loginWithQrStart !== "function" || typeof qrGatewayResult?.gateway?.loginWithQrWait !== "function") {
239
45
  throw missingHelperError("qrGateway");
240
46
  }
241
- if (typeof publicHelpers?.getUpdates !== "function") {
47
+ if (typeof updatesSend?.getUpdates !== "function") {
242
48
  throw missingHelperError("getUpdates");
243
49
  }
244
50
  if (typeof accountHelpers?.listAccountIds !== "function" ||
@@ -246,7 +52,7 @@ export async function loadOpenClawWeixinPublicHelpers(loaders = {}) {
246
52
  typeof accountHelpers?.describeAccount !== "function") {
247
53
  throw missingHelperError("accountHelpers");
248
54
  }
249
- if (typeof sendHelper?.sendMessageWeixin !== "function") {
55
+ if (typeof updatesSend?.sendMessageWeixin !== "function") {
250
56
  throw missingHelperError("sendMessageWeixin");
251
57
  }
252
58
  return {
@@ -255,8 +61,8 @@ export async function loadOpenClawWeixinPublicHelpers(loaders = {}) {
255
61
  qrGateway: qrGatewayResult.gateway,
256
62
  accountHelpers,
257
63
  latestAccountState,
258
- getUpdates: publicHelpers.getUpdates,
259
- sendMessageWeixin: sendHelper.sendMessageWeixin,
64
+ getUpdates: updatesSend.getUpdates,
65
+ sendMessageWeixin: updatesSend.sendMessageWeixin,
260
66
  persistGetUpdatesBuf: syncBufHelpers.persistGetUpdatesBuf,
261
67
  };
262
68
  }
@@ -0,0 +1,15 @@
1
+ export type WeixinQrGateway = {
2
+ loginWithQrStart: (input?: unknown) => unknown;
3
+ loginWithQrWait: (input?: unknown) => unknown;
4
+ };
5
+ type OpenClawQrGatewayPayload = {
6
+ plugin?: unknown;
7
+ };
8
+ export declare function createOpenClawQrGateway(source: WeixinQrGateway): WeixinQrGateway;
9
+ export declare function loadOpenClawQrGateway(payloads: OpenClawQrGatewayPayload[], options?: {
10
+ pluginId?: string;
11
+ }): Promise<{
12
+ gateway: WeixinQrGateway;
13
+ pluginId: string;
14
+ }>;
15
+ export {};
@@ -0,0 +1,39 @@
1
+ function toObjectInput(input) {
2
+ return input && typeof input === "object" ? input : {};
3
+ }
4
+ function hasQrLoginMethods(value) {
5
+ if (!value || typeof value !== "object") {
6
+ return false;
7
+ }
8
+ const candidate = value;
9
+ return typeof candidate.loginWithQrStart === "function" && typeof candidate.loginWithQrWait === "function";
10
+ }
11
+ export function createOpenClawQrGateway(source) {
12
+ return {
13
+ async loginWithQrStart(input) {
14
+ return source.loginWithQrStart(toObjectInput(input));
15
+ },
16
+ async loginWithQrWait(input) {
17
+ return source.loginWithQrWait(toObjectInput(input));
18
+ },
19
+ };
20
+ }
21
+ export async function loadOpenClawQrGateway(payloads, options = {}) {
22
+ for (const payload of payloads) {
23
+ const payloadPlugin = payload?.plugin;
24
+ const resolvedPluginId = typeof options.pluginId === "string" && options.pluginId.trim().length > 0
25
+ ? options.pluginId
26
+ : typeof payloadPlugin?.id === "string" &&
27
+ String(payloadPlugin.id).trim().length > 0
28
+ ? String(payloadPlugin.id)
29
+ : "unknown";
30
+ const gateway = payloadPlugin && typeof payloadPlugin === "object" ? payloadPlugin.gateway : null;
31
+ if (hasQrLoginMethods(gateway)) {
32
+ return {
33
+ gateway: createOpenClawQrGateway(gateway),
34
+ pluginId: resolvedPluginId,
35
+ };
36
+ }
37
+ }
38
+ throw new Error("registerChannel did not expose weixin gateway loginWithQrStart/loginWithQrWait");
39
+ }
@@ -0,0 +1,24 @@
1
+ export type PublicWeixinPersistGetUpdatesBuf = (params: {
2
+ accountId: string;
3
+ getUpdatesBuf: string;
4
+ }) => Promise<void>;
5
+ export declare function createOpenClawSyncBufHelper(input: {
6
+ getSyncBufFilePath: (accountId: string) => string;
7
+ saveGetUpdatesBuf: (filePath: string, getUpdatesBuf: string) => void;
8
+ }): {
9
+ persistGetUpdatesBuf: PublicWeixinPersistGetUpdatesBuf;
10
+ };
11
+ export declare function loadOpenClawSyncBufHelper(options?: {
12
+ syncBufModulePath?: string;
13
+ }): Promise<{
14
+ persistGetUpdatesBuf: PublicWeixinPersistGetUpdatesBuf;
15
+ }>;
16
+ export declare function loadLatestWeixinAccountState(options?: {
17
+ stateDirModulePath?: string;
18
+ syncBufModulePath?: string;
19
+ }): Promise<{
20
+ accountId: string;
21
+ token: string;
22
+ baseUrl: string;
23
+ getUpdatesBuf?: string;
24
+ } | null>;
@@ -0,0 +1,86 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ import { createJiti } from "jiti";
5
+ const OPENCLAW_SYNC_BUF_MODULE = "@tencent-weixin/openclaw-weixin/src/storage/sync-buf.ts";
6
+ const OPENCLAW_STATE_DIR_MODULE = "@tencent-weixin/openclaw-weixin/src/storage/state-dir.ts";
7
+ let syncBufJitiLoader = null;
8
+ function getSyncBufJiti() {
9
+ if (syncBufJitiLoader) {
10
+ return syncBufJitiLoader;
11
+ }
12
+ syncBufJitiLoader = createJiti(import.meta.url, {
13
+ interopDefault: true,
14
+ extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs", ".json"],
15
+ });
16
+ return syncBufJitiLoader;
17
+ }
18
+ export function createOpenClawSyncBufHelper(input) {
19
+ return {
20
+ async persistGetUpdatesBuf({ accountId, getUpdatesBuf }) {
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
+ }
25
+ input.saveGetUpdatesBuf(filePath, getUpdatesBuf);
26
+ },
27
+ };
28
+ }
29
+ export async function loadOpenClawSyncBufHelper(options = {}) {
30
+ const require = createRequire(import.meta.url);
31
+ const syncBufModulePath = require.resolve(options.syncBufModulePath ?? OPENCLAW_SYNC_BUF_MODULE);
32
+ const syncBufModule = getSyncBufJiti()(syncBufModulePath);
33
+ if (typeof syncBufModule.getSyncBufFilePath !== "function" || typeof syncBufModule.saveGetUpdatesBuf !== "function") {
34
+ throw new Error("[wechat-compat] sync-buf source helper unavailable");
35
+ }
36
+ return createOpenClawSyncBufHelper({
37
+ getSyncBufFilePath: syncBufModule.getSyncBufFilePath,
38
+ saveGetUpdatesBuf: syncBufModule.saveGetUpdatesBuf,
39
+ });
40
+ }
41
+ export async function loadLatestWeixinAccountState(options = {}) {
42
+ const require = createRequire(import.meta.url);
43
+ const stateDirModulePath = require.resolve(options.stateDirModulePath ?? OPENCLAW_STATE_DIR_MODULE);
44
+ const stateDirModule = getSyncBufJiti()(stateDirModulePath);
45
+ const stateDir = stateDirModule.resolveStateDir?.();
46
+ if (!stateDir) {
47
+ return null;
48
+ }
49
+ const accountsIndexPath = path.join(stateDir, "openclaw-weixin", "accounts.json");
50
+ let accountIds = [];
51
+ try {
52
+ const raw = await readFile(accountsIndexPath, "utf8");
53
+ const parsed = JSON.parse(raw);
54
+ if (Array.isArray(parsed)) {
55
+ accountIds = parsed.filter((item) => typeof item === "string" && item.trim().length > 0);
56
+ }
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ const accountId = accountIds.at(-1);
62
+ if (!accountId) {
63
+ return null;
64
+ }
65
+ try {
66
+ const accountFilePath = path.join(stateDir, "openclaw-weixin", "accounts", `${accountId}.json`);
67
+ const accountRaw = await readFile(accountFilePath, "utf8");
68
+ const account = JSON.parse(accountRaw);
69
+ const syncBufModulePath = require.resolve(options.syncBufModulePath ?? OPENCLAW_SYNC_BUF_MODULE);
70
+ const syncBufModule = getSyncBufJiti()(syncBufModulePath);
71
+ if (typeof account.token !== "string" || account.token.trim().length === 0) {
72
+ return null;
73
+ }
74
+ const syncBufFilePath = syncBufModule.getSyncBufFilePath?.(accountId);
75
+ const persistedGetUpdatesBuf = syncBufFilePath ? syncBufModule.loadGetUpdatesBuf?.(syncBufFilePath) : undefined;
76
+ return {
77
+ accountId,
78
+ token: account.token,
79
+ baseUrl: typeof account.baseUrl === "string" && account.baseUrl.trim().length > 0 ? account.baseUrl : "https://ilinkai.weixin.qq.com",
80
+ getUpdatesBuf: typeof persistedGetUpdatesBuf === "string" ? persistedGetUpdatesBuf : undefined,
81
+ };
82
+ }
83
+ catch {
84
+ return null;
85
+ }
86
+ }
@@ -0,0 +1,47 @@
1
+ type PublicWeixinMessageItem = {
2
+ type?: number;
3
+ text_item?: {
4
+ text?: string;
5
+ };
6
+ };
7
+ export type PublicWeixinMessage = {
8
+ message_id?: number;
9
+ from_user_id?: string;
10
+ context_token?: string;
11
+ create_time_ms?: number;
12
+ item_list?: PublicWeixinMessageItem[];
13
+ };
14
+ export type PublicWeixinSendMessage = (params: {
15
+ to: string;
16
+ text: string;
17
+ opts: {
18
+ baseUrl: string;
19
+ token: string;
20
+ contextToken?: string;
21
+ };
22
+ }) => Promise<{
23
+ messageId: string;
24
+ }>;
25
+ type PublicGetUpdates = (params: {
26
+ baseUrl: string;
27
+ token?: string;
28
+ get_updates_buf?: string;
29
+ timeoutMs?: number;
30
+ }) => Promise<{
31
+ msgs?: PublicWeixinMessage[];
32
+ get_updates_buf?: string;
33
+ }>;
34
+ export declare function createOpenClawUpdatesHelper(getUpdates: PublicGetUpdates): {
35
+ getUpdates: PublicGetUpdates;
36
+ };
37
+ export declare function createOpenClawSendHelper(sendMessageWeixin: PublicWeixinSendMessage): {
38
+ sendMessageWeixin: PublicWeixinSendMessage;
39
+ };
40
+ export declare function loadOpenClawUpdatesAndSendHelpers(options?: {
41
+ getUpdatesModulePath?: string;
42
+ sendMessageWeixinModulePath?: string;
43
+ }): Promise<{
44
+ getUpdates: PublicGetUpdates;
45
+ sendMessageWeixin: PublicWeixinSendMessage;
46
+ }>;
47
+ export {};
@@ -0,0 +1,49 @@
1
+ import { createRequire } from "node:module";
2
+ import { createJiti } from "jiti";
3
+ const OPENCLAW_UPDATES_MODULE = "@tencent-weixin/openclaw-weixin/src/api/api.ts";
4
+ const OPENCLAW_SEND_MODULE = "@tencent-weixin/openclaw-weixin/src/messaging/send.ts";
5
+ let updatesSendJitiLoader = null;
6
+ function getUpdatesSendJiti() {
7
+ if (updatesSendJitiLoader) {
8
+ return updatesSendJitiLoader;
9
+ }
10
+ updatesSendJitiLoader = createJiti(import.meta.url, {
11
+ interopDefault: true,
12
+ extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs", ".json"],
13
+ });
14
+ return updatesSendJitiLoader;
15
+ }
16
+ function toObjectInput(input) {
17
+ return input && typeof input === "object" ? input : {};
18
+ }
19
+ export function createOpenClawUpdatesHelper(getUpdates) {
20
+ return {
21
+ async getUpdates(input) {
22
+ return getUpdates(toObjectInput(input));
23
+ },
24
+ };
25
+ }
26
+ export function createOpenClawSendHelper(sendMessageWeixin) {
27
+ return {
28
+ async sendMessageWeixin(input) {
29
+ return sendMessageWeixin(toObjectInput(input));
30
+ },
31
+ };
32
+ }
33
+ export async function loadOpenClawUpdatesAndSendHelpers(options = {}) {
34
+ const require = createRequire(import.meta.url);
35
+ const getUpdatesModulePath = require.resolve(options.getUpdatesModulePath ?? OPENCLAW_UPDATES_MODULE);
36
+ const sendModulePath = require.resolve(options.sendMessageWeixinModulePath ?? OPENCLAW_SEND_MODULE);
37
+ const getUpdatesModule = getUpdatesSendJiti()(getUpdatesModulePath);
38
+ const sendModule = getUpdatesSendJiti()(sendModulePath);
39
+ if (typeof getUpdatesModule.getUpdates !== "function") {
40
+ throw new Error("public getUpdates helper unavailable");
41
+ }
42
+ if (typeof sendModule.sendMessageWeixin !== "function") {
43
+ throw new Error("public sendMessageWeixin helper unavailable");
44
+ }
45
+ return {
46
+ ...createOpenClawUpdatesHelper(getUpdatesModule.getUpdates),
47
+ ...createOpenClawSendHelper(sendModule.sendMessageWeixin),
48
+ };
49
+ }
@@ -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.5",
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",
@@ -63,8 +63,8 @@
63
63
  "dependencies": {
64
64
  "@opencode-ai/plugin": "^1.2.26",
65
65
  "@opencode-ai/sdk": "^1.2.26",
66
- "@tencent-weixin/openclaw-weixin": "^1.0.3",
67
- "openclaw": "2026.3.13",
66
+ "@tencent-weixin/openclaw-weixin": "2.0.1",
67
+ "openclaw": "2026.3.22",
68
68
  "xdg-basedir": "^5.1.0"
69
69
  }
70
70
  }