opencode-copilot-account-switcher 0.14.29 → 0.14.31

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 (35) hide show
  1. package/dist/common-settings-store.d.ts +13 -0
  2. package/dist/common-settings-store.js +14 -0
  3. package/dist/wechat/bridge.d.ts +2 -0
  4. package/dist/wechat/bridge.js +122 -8
  5. package/dist/wechat/broker-client.js +33 -1
  6. package/dist/wechat/broker-entry.d.ts +9 -18
  7. package/dist/wechat/broker-entry.js +104 -18
  8. package/dist/wechat/broker-server.js +106 -0
  9. package/dist/wechat/command-parser.d.ts +2 -0
  10. package/dist/wechat/command-parser.js +21 -10
  11. package/dist/wechat/compat/jiti-loader.d.ts +3 -2
  12. package/dist/wechat/compat/jiti-loader.js +2 -3
  13. package/dist/wechat/compat/openclaw-account-helpers.js +3 -3
  14. package/dist/wechat/compat/openclaw-public-entry.js +3 -3
  15. package/dist/wechat/compat/openclaw-sync-buf.js +5 -5
  16. package/dist/wechat/compat/openclaw-updates-send.js +5 -4
  17. package/dist/wechat/handle.d.ts +1 -0
  18. package/dist/wechat/handle.js +6 -1
  19. package/dist/wechat/notification-dispatcher.d.ts +12 -0
  20. package/dist/wechat/notification-dispatcher.js +143 -0
  21. package/dist/wechat/notification-format.d.ts +2 -0
  22. package/dist/wechat/notification-format.js +17 -0
  23. package/dist/wechat/notification-store.d.ts +25 -0
  24. package/dist/wechat/notification-store.js +290 -0
  25. package/dist/wechat/notification-types.d.ts +17 -0
  26. package/dist/wechat/notification-types.js +1 -0
  27. package/dist/wechat/protocol.d.ts +16 -1
  28. package/dist/wechat/protocol.js +1 -0
  29. package/dist/wechat/request-store.d.ts +15 -0
  30. package/dist/wechat/request-store.js +71 -1
  31. package/dist/wechat/state-paths.d.ts +2 -0
  32. package/dist/wechat/state-paths.js +7 -0
  33. package/dist/wechat/wechat-status-runtime.d.ts +9 -0
  34. package/dist/wechat/wechat-status-runtime.js +27 -0
  35. package/package.json +1 -1
@@ -6,26 +6,37 @@ export function parseWechatSlashCommand(input) {
6
6
  if (normalized === "/status") {
7
7
  return { type: "status" };
8
8
  }
9
- if (normalized.startsWith("/reply")) {
10
- const text = normalized.slice("/reply".length).trim();
11
- if (!text) {
9
+ const parts = normalized.split(/\s+/);
10
+ const command = parts[0];
11
+ if (command === "/reply") {
12
+ if (parts.length < 3) {
12
13
  return null;
13
14
  }
14
- return { type: "reply", text };
15
+ const handle = parts[1];
16
+ const textParts = parts.slice(2);
17
+ const text = textParts.join(" ").trim();
18
+ if (!handle || !text) {
19
+ return null;
20
+ }
21
+ return { type: "reply", handle, text };
15
22
  }
16
- if (normalized.startsWith("/allow")) {
17
- const rest = normalized.slice("/allow".length).trim();
18
- if (!rest) {
23
+ if (command === "/allow") {
24
+ if (parts.length < 3) {
25
+ return null;
26
+ }
27
+ const handle = parts[1];
28
+ const rawReply = parts[2];
29
+ const messageParts = parts.slice(3);
30
+ if (!handle || !rawReply) {
19
31
  return null;
20
32
  }
21
- const [rawReply, ...messageParts] = rest.split(/\s+/);
22
33
  if (rawReply !== "once" && rawReply !== "always" && rawReply !== "reject") {
23
34
  return null;
24
35
  }
25
36
  const message = messageParts.join(" ").trim();
26
37
  return message.length > 0
27
- ? { type: "allow", reply: rawReply, message }
28
- : { type: "allow", reply: rawReply };
38
+ ? { type: "allow", handle, reply: rawReply, message }
39
+ : { type: "allow", handle, reply: rawReply };
29
40
  }
30
41
  return null;
31
42
  }
@@ -1,11 +1,12 @@
1
1
  export type JitiLoader = (path: string) => unknown;
2
2
  type CreateJiti = (id: string | URL, options?: Record<string, unknown>) => JitiLoader;
3
+ type JitiImport = (specifier: string) => Promise<unknown> | unknown;
3
4
  type JitiNamespace = {
4
5
  createJiti?: unknown;
5
6
  default?: unknown;
6
7
  };
7
8
  export declare function resolveCreateJiti(namespace: JitiNamespace): CreateJiti;
8
- export declare function loadJiti(requireImpl?: NodeRequire): {
9
+ export declare function loadJiti(importImpl?: JitiImport): Promise<{
9
10
  createJiti: CreateJiti;
10
- };
11
+ }>;
11
12
  export {};
@@ -1,4 +1,3 @@
1
- import { createRequire } from "node:module";
2
1
  function isCreateJiti(value) {
3
2
  return typeof value === "function";
4
3
  }
@@ -19,8 +18,8 @@ export function resolveCreateJiti(namespace) {
19
18
  }
20
19
  throw new Error("[wechat-compat] createJiti export unavailable");
21
20
  }
22
- export function loadJiti(requireImpl = createRequire(import.meta.url)) {
23
- const namespace = requireImpl("jiti");
21
+ export async function loadJiti(importImpl = (specifier) => import(specifier)) {
22
+ const namespace = await Promise.resolve(importImpl("jiti"));
24
23
  return {
25
24
  createJiti: resolveCreateJiti(namespace),
26
25
  };
@@ -2,11 +2,11 @@ import { createRequire } from "node:module";
2
2
  import { loadJiti } from "./jiti-loader.js";
3
3
  const OPENCLAW_WEIXIN_ACCOUNTS_MODULE = "@tencent-weixin/openclaw-weixin/src/auth/accounts.ts";
4
4
  let accountJitiLoader = null;
5
- function getAccountJiti() {
5
+ async function getAccountJiti() {
6
6
  if (accountJitiLoader) {
7
7
  return accountJitiLoader;
8
8
  }
9
- accountJitiLoader = loadJiti().createJiti(import.meta.url, {
9
+ accountJitiLoader = (await loadJiti()).createJiti(import.meta.url, {
10
10
  interopDefault: true,
11
11
  extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs", ".json"],
12
12
  });
@@ -52,7 +52,7 @@ export function createOpenClawAccountHelpers(input) {
52
52
  export async function loadOpenClawAccountHelpers(options = {}) {
53
53
  const require = createRequire(import.meta.url);
54
54
  const accountsModulePath = require.resolve(options.accountsModulePath ?? OPENCLAW_WEIXIN_ACCOUNTS_MODULE);
55
- const accountsModule = getAccountJiti()(accountsModulePath);
55
+ const accountsModule = (await getAccountJiti())(accountsModulePath);
56
56
  if (typeof accountsModule.listIndexedWeixinAccountIds !== "function" || typeof accountsModule.loadWeixinAccount !== "function") {
57
57
  throw new Error("[wechat-compat] account source helper unavailable");
58
58
  }
@@ -8,11 +8,11 @@ function requireField(condition, message) {
8
8
  throw new Error(`[wechat-compat] ${message}`);
9
9
  }
10
10
  }
11
- function getPublicJiti() {
11
+ async function getPublicJiti() {
12
12
  if (publicJitiLoader) {
13
13
  return publicJitiLoader;
14
14
  }
15
- publicJitiLoader = loadJiti().createJiti(import.meta.url, {
15
+ publicJitiLoader = (await loadJiti()).createJiti(import.meta.url, {
16
16
  interopDefault: true,
17
17
  extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs", ".json"],
18
18
  });
@@ -42,7 +42,7 @@ export async function resolveOpenClawWeixinPublicEntry() {
42
42
  }
43
43
  export async function loadOpenClawWeixinDefaultExport() {
44
44
  const entry = await resolveOpenClawWeixinPublicEntry();
45
- const moduleNamespace = getPublicJiti()(entry.entryAbsolutePath);
45
+ const moduleNamespace = (await getPublicJiti())(entry.entryAbsolutePath);
46
46
  const plugin = moduleNamespace.default;
47
47
  if (!plugin || typeof plugin !== "object" || typeof plugin.register !== "function") {
48
48
  throw new Error("[wechat-compat] @tencent-weixin/openclaw-weixin public entry default export is missing register(api)");
@@ -5,11 +5,11 @@ import { loadJiti } from "./jiti-loader.js";
5
5
  const OPENCLAW_SYNC_BUF_MODULE = "@tencent-weixin/openclaw-weixin/src/storage/sync-buf.ts";
6
6
  const OPENCLAW_STATE_DIR_MODULE = "@tencent-weixin/openclaw-weixin/src/storage/state-dir.ts";
7
7
  let syncBufJitiLoader = null;
8
- function getSyncBufJiti() {
8
+ async function getSyncBufJiti() {
9
9
  if (syncBufJitiLoader) {
10
10
  return syncBufJitiLoader;
11
11
  }
12
- syncBufJitiLoader = loadJiti().createJiti(import.meta.url, {
12
+ syncBufJitiLoader = (await loadJiti()).createJiti(import.meta.url, {
13
13
  interopDefault: true,
14
14
  extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs", ".json"],
15
15
  });
@@ -29,7 +29,7 @@ export function createOpenClawSyncBufHelper(input) {
29
29
  export async function loadOpenClawSyncBufHelper(options = {}) {
30
30
  const require = createRequire(import.meta.url);
31
31
  const syncBufModulePath = require.resolve(options.syncBufModulePath ?? OPENCLAW_SYNC_BUF_MODULE);
32
- const syncBufModule = getSyncBufJiti()(syncBufModulePath);
32
+ const syncBufModule = (await getSyncBufJiti())(syncBufModulePath);
33
33
  if (typeof syncBufModule.getSyncBufFilePath !== "function" || typeof syncBufModule.saveGetUpdatesBuf !== "function") {
34
34
  throw new Error("[wechat-compat] sync-buf source helper unavailable");
35
35
  }
@@ -41,7 +41,7 @@ export async function loadOpenClawSyncBufHelper(options = {}) {
41
41
  export async function loadLatestWeixinAccountState(options = {}) {
42
42
  const require = createRequire(import.meta.url);
43
43
  const stateDirModulePath = require.resolve(options.stateDirModulePath ?? OPENCLAW_STATE_DIR_MODULE);
44
- const stateDirModule = getSyncBufJiti()(stateDirModulePath);
44
+ const stateDirModule = (await getSyncBufJiti())(stateDirModulePath);
45
45
  const stateDir = stateDirModule.resolveStateDir?.();
46
46
  if (!stateDir) {
47
47
  return null;
@@ -67,7 +67,7 @@ export async function loadLatestWeixinAccountState(options = {}) {
67
67
  const accountRaw = await readFile(accountFilePath, "utf8");
68
68
  const account = JSON.parse(accountRaw);
69
69
  const syncBufModulePath = require.resolve(options.syncBufModulePath ?? OPENCLAW_SYNC_BUF_MODULE);
70
- const syncBufModule = getSyncBufJiti()(syncBufModulePath);
70
+ const syncBufModule = (await getSyncBufJiti())(syncBufModulePath);
71
71
  if (typeof account.token !== "string" || account.token.trim().length === 0) {
72
72
  return null;
73
73
  }
@@ -3,11 +3,11 @@ import { loadJiti } from "./jiti-loader.js";
3
3
  const OPENCLAW_UPDATES_MODULE = "@tencent-weixin/openclaw-weixin/src/api/api.ts";
4
4
  const OPENCLAW_SEND_MODULE = "@tencent-weixin/openclaw-weixin/src/messaging/send.ts";
5
5
  let updatesSendJitiLoader = null;
6
- function getUpdatesSendJiti() {
6
+ async function getUpdatesSendJiti() {
7
7
  if (updatesSendJitiLoader) {
8
8
  return updatesSendJitiLoader;
9
9
  }
10
- updatesSendJitiLoader = loadJiti().createJiti(import.meta.url, {
10
+ updatesSendJitiLoader = (await loadJiti()).createJiti(import.meta.url, {
11
11
  interopDefault: true,
12
12
  extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs", ".json"],
13
13
  });
@@ -34,8 +34,9 @@ export async function loadOpenClawUpdatesAndSendHelpers(options = {}) {
34
34
  const require = createRequire(import.meta.url);
35
35
  const getUpdatesModulePath = require.resolve(options.getUpdatesModulePath ?? OPENCLAW_UPDATES_MODULE);
36
36
  const sendModulePath = require.resolve(options.sendMessageWeixinModulePath ?? OPENCLAW_SEND_MODULE);
37
- const getUpdatesModule = getUpdatesSendJiti()(getUpdatesModulePath);
38
- const sendModule = getUpdatesSendJiti()(sendModulePath);
37
+ const jiti = await getUpdatesSendJiti();
38
+ const getUpdatesModule = jiti(getUpdatesModulePath);
39
+ const sendModule = jiti(sendModulePath);
39
40
  if (typeof getUpdatesModule.getUpdates !== "function") {
40
41
  throw new Error("public getUpdates helper unavailable");
41
42
  }
@@ -2,6 +2,7 @@ import type { WechatRequestKind } from "./state-paths.js";
2
2
  export declare function createRouteKey(input: {
3
3
  kind: WechatRequestKind;
4
4
  requestID: string;
5
+ scopeKey?: string;
5
6
  }): string;
6
7
  export declare function normalizeHandle(input: string): string;
7
8
  export declare function assertValidHandleInput(input: string): void;
@@ -8,7 +8,12 @@ function normalizeRequestID(requestID) {
8
8
  }
9
9
  export function createRouteKey(input) {
10
10
  const normalized = normalizeRequestID(input.requestID);
11
- const digest = crypto.createHash("sha1").update(`${input.kind}:${normalized}`).digest("hex").slice(0, 12);
11
+ const normalizedScope = typeof input.scopeKey === "string" ? input.scopeKey.trim().toLowerCase() : "";
12
+ const digest = crypto
13
+ .createHash("sha1")
14
+ .update(`${input.kind}:${normalized}:${normalizedScope}`)
15
+ .digest("hex")
16
+ .slice(0, 12);
12
17
  return `${input.kind}-${digest}`;
13
18
  }
14
19
  export function normalizeHandle(input) {
@@ -0,0 +1,12 @@
1
+ export type WechatNotificationSendInput = {
2
+ to: string;
3
+ text: string;
4
+ };
5
+ type CreateWechatNotificationDispatcherInput = {
6
+ sendMessage: (input: WechatNotificationSendInput) => Promise<unknown>;
7
+ };
8
+ type WechatNotificationDispatcher = {
9
+ drainOutboundMessages: () => Promise<void>;
10
+ };
11
+ export declare function createWechatNotificationDispatcher(input: CreateWechatNotificationDispatcherInput): WechatNotificationDispatcher;
12
+ export {};
@@ -0,0 +1,143 @@
1
+ import { readCommonSettingsStore } from "../common-settings-store.js";
2
+ import { listPendingNotifications, markNotificationResolved, markNotificationFailed, markNotificationSent, purgeTerminalNotificationsBefore, } from "./notification-store.js";
3
+ import { formatWechatNotificationText } from "./notification-format.js";
4
+ import { findRequestByRouteKey } from "./request-store.js";
5
+ const DEFAULT_NOTIFICATION_TERMINAL_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
6
+ function toPositiveNumber(rawValue, fallback) {
7
+ if (typeof rawValue !== "string" || rawValue.trim().length === 0) {
8
+ return fallback;
9
+ }
10
+ const parsed = Number(rawValue);
11
+ if (!Number.isFinite(parsed) || parsed <= 0) {
12
+ return fallback;
13
+ }
14
+ return parsed;
15
+ }
16
+ function shouldSendKind(kind, notifications) {
17
+ if (!notifications.enabled) {
18
+ return false;
19
+ }
20
+ if (kind === "question") {
21
+ return notifications.question;
22
+ }
23
+ if (kind === "permission") {
24
+ return notifications.permission;
25
+ }
26
+ return notifications.sessionError;
27
+ }
28
+ function toErrorMessage(error) {
29
+ if (error instanceof Error) {
30
+ return error.message;
31
+ }
32
+ if (typeof error === "string") {
33
+ return error;
34
+ }
35
+ return String(error);
36
+ }
37
+ function isNotPendingStateError(error) {
38
+ if (!(error instanceof Error)) {
39
+ return false;
40
+ }
41
+ return /not pending/i.test(error.message);
42
+ }
43
+ function isNotSuppressibleStateError(error) {
44
+ if (!(error instanceof Error)) {
45
+ return false;
46
+ }
47
+ return /not pending|neither pending nor sent/i.test(error.message);
48
+ }
49
+ async function shouldSuppressPendingNotification(record) {
50
+ if (record.kind === "sessionError") {
51
+ return false;
52
+ }
53
+ if (typeof record.routeKey !== "string" || record.routeKey.trim().length === 0) {
54
+ return false;
55
+ }
56
+ const request = await findRequestByRouteKey({
57
+ kind: record.kind,
58
+ routeKey: record.routeKey,
59
+ });
60
+ if (!request) {
61
+ return false;
62
+ }
63
+ return request.status !== "open";
64
+ }
65
+ export function createWechatNotificationDispatcher(input) {
66
+ return {
67
+ drainOutboundMessages: async () => {
68
+ const retentionMs = toPositiveNumber(process.env.WECHAT_NOTIFICATION_TERMINAL_RETENTION_MS, DEFAULT_NOTIFICATION_TERMINAL_RETENTION_MS);
69
+ await purgeTerminalNotificationsBefore({
70
+ cutoffAt: Date.now() - retentionMs,
71
+ });
72
+ const settings = await readCommonSettingsStore();
73
+ const notifications = settings.wechat?.notifications;
74
+ const targetUserId = settings.wechat?.primaryBinding?.userId;
75
+ const targetAccountId = settings.wechat?.primaryBinding?.accountId;
76
+ if (!notifications) {
77
+ return;
78
+ }
79
+ if (typeof targetUserId !== "string" || targetUserId.trim().length === 0) {
80
+ return;
81
+ }
82
+ if (typeof targetAccountId !== "string" || targetAccountId.trim().length === 0) {
83
+ return;
84
+ }
85
+ const pending = await listPendingNotifications();
86
+ for (const record of pending) {
87
+ if (await shouldSuppressPendingNotification(record)) {
88
+ try {
89
+ await markNotificationResolved({
90
+ idempotencyKey: record.idempotencyKey,
91
+ resolvedAt: Date.now(),
92
+ suppressed: true,
93
+ });
94
+ }
95
+ catch (error) {
96
+ if (!isNotSuppressibleStateError(error)) {
97
+ throw error;
98
+ }
99
+ }
100
+ continue;
101
+ }
102
+ if (!shouldSendKind(record.kind, notifications)) {
103
+ continue;
104
+ }
105
+ if (record.userId !== targetUserId || record.wechatAccountId !== targetAccountId) {
106
+ continue;
107
+ }
108
+ try {
109
+ await input.sendMessage({
110
+ to: targetUserId,
111
+ text: formatWechatNotificationText(record),
112
+ });
113
+ }
114
+ catch (error) {
115
+ try {
116
+ await markNotificationFailed({
117
+ idempotencyKey: record.idempotencyKey,
118
+ failedAt: Date.now(),
119
+ reason: toErrorMessage(error),
120
+ });
121
+ }
122
+ catch (markError) {
123
+ if (!isNotPendingStateError(markError)) {
124
+ throw markError;
125
+ }
126
+ }
127
+ continue;
128
+ }
129
+ try {
130
+ await markNotificationSent({
131
+ idempotencyKey: record.idempotencyKey,
132
+ sentAt: Date.now(),
133
+ });
134
+ }
135
+ catch (error) {
136
+ if (!isNotPendingStateError(error)) {
137
+ throw error;
138
+ }
139
+ }
140
+ }
141
+ },
142
+ };
143
+ }
@@ -0,0 +1,2 @@
1
+ import type { NotificationRecord } from "./notification-types.js";
2
+ export declare function formatWechatNotificationText(record: NotificationRecord): string;
@@ -0,0 +1,17 @@
1
+ function formatHandle(handle, fallback) {
2
+ if (typeof handle === "string" && handle.trim().length > 0) {
3
+ return handle;
4
+ }
5
+ return fallback;
6
+ }
7
+ export function formatWechatNotificationText(record) {
8
+ if (record.kind === "question") {
9
+ const handle = formatHandle(record.handle, "q?");
10
+ return `收到新的问题请求(${handle}),请在 OpenCode 中处理。`;
11
+ }
12
+ if (record.kind === "permission") {
13
+ const handle = formatHandle(record.handle, "p?");
14
+ return `收到新的权限请求(${handle}),请在 OpenCode 中处理。`;
15
+ }
16
+ return "检测到会话异常(retry),请在 OpenCode 中检查并处理。";
17
+ }
@@ -0,0 +1,25 @@
1
+ import { type NotificationKind, type NotificationRecord } from "./notification-types.js";
2
+ export declare function upsertNotification(input: Omit<NotificationRecord, "status" | "sentAt" | "resolvedAt" | "failedAt" | "suppressedAt" | "failureReason">): Promise<NotificationRecord>;
3
+ export declare function markNotificationSent(input: {
4
+ idempotencyKey: string;
5
+ sentAt: number;
6
+ }): Promise<NotificationRecord>;
7
+ export declare function markNotificationResolved(input: {
8
+ idempotencyKey: string;
9
+ resolvedAt: number;
10
+ suppressed?: boolean;
11
+ }): Promise<NotificationRecord>;
12
+ export declare function markNotificationFailed(input: {
13
+ idempotencyKey: string;
14
+ failedAt: number;
15
+ reason: string;
16
+ }): Promise<NotificationRecord>;
17
+ export declare function listPendingNotifications(): Promise<NotificationRecord[]>;
18
+ export declare function findSentNotificationByRequest(input: {
19
+ kind: Exclude<NotificationKind, "sessionError">;
20
+ routeKey: string;
21
+ handle: string;
22
+ }): Promise<NotificationRecord | undefined>;
23
+ export declare function purgeTerminalNotificationsBefore(input: {
24
+ cutoffAt: number;
25
+ }): Promise<number>;