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