gewe-openclaw 2026.3.13 → 2026.3.23

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 (44) hide show
  1. package/README.md +455 -3
  2. package/index.ts +39 -1
  3. package/package.json +12 -1
  4. package/skills/gewe-agent-tools/SKILL.md +113 -0
  5. package/skills/gewe-channel-rules/SKILL.md +7 -0
  6. package/src/accounts.ts +51 -5
  7. package/src/api-tools.ts +1264 -0
  8. package/src/api.ts +37 -2
  9. package/src/binary-command.ts +65 -0
  10. package/src/channel-actions.ts +536 -0
  11. package/src/channel-allowlist.ts +150 -0
  12. package/src/channel-directory.ts +419 -0
  13. package/src/channel-status.ts +186 -0
  14. package/src/channel.ts +155 -58
  15. package/src/config-edit.ts +94 -0
  16. package/src/config-schema.ts +78 -3
  17. package/src/contacts-api.ts +113 -0
  18. package/src/delivery.ts +502 -62
  19. package/src/directory-cache.ts +164 -0
  20. package/src/gewe-account-api.ts +27 -0
  21. package/src/group-allowlist-tool.ts +242 -0
  22. package/src/group-binding-tool.ts +154 -0
  23. package/src/group-binding.ts +405 -0
  24. package/src/groups-api.ts +146 -0
  25. package/src/inbound-batch.ts +5 -2
  26. package/src/inbound.ts +248 -41
  27. package/src/media-server.ts +73 -93
  28. package/src/moments-api.ts +138 -0
  29. package/src/monitor.ts +81 -24
  30. package/src/onboarding.ts +9 -4
  31. package/src/openclaw-compat.ts +1070 -0
  32. package/src/pairing-store.ts +478 -0
  33. package/src/personal-api.ts +45 -0
  34. package/src/policy.ts +130 -22
  35. package/src/quote-context-cache.ts +97 -0
  36. package/src/reply-options.ts +101 -2
  37. package/src/s3.ts +1 -1
  38. package/src/send.ts +235 -16
  39. package/src/setup-wizard-types.ts +162 -0
  40. package/src/setup-wizard.ts +464 -0
  41. package/src/silk.ts +2 -1
  42. package/src/state-paths.ts +55 -14
  43. package/src/types.ts +66 -7
  44. package/src/xml.ts +158 -0
@@ -0,0 +1,164 @@
1
+ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "./openclaw-compat.js";
2
+
3
+ type NamedEntry = {
4
+ id: string;
5
+ name?: string;
6
+ lastSeenAt: number;
7
+ };
8
+
9
+ type AccountDirectoryCache = {
10
+ users: Map<string, NamedEntry>;
11
+ groups: Map<string, NamedEntry>;
12
+ groupMembers: Map<string, Map<string, NamedEntry>>;
13
+ };
14
+
15
+ const directoryCache = new Map<string, AccountDirectoryCache>();
16
+
17
+ function resolveAccountCache(accountId?: string | null): AccountDirectoryCache {
18
+ const normalized = normalizeAccountId(accountId ?? DEFAULT_ACCOUNT_ID);
19
+ let existing = directoryCache.get(normalized);
20
+ if (!existing) {
21
+ existing = {
22
+ users: new Map(),
23
+ groups: new Map(),
24
+ groupMembers: new Map(),
25
+ };
26
+ directoryCache.set(normalized, existing);
27
+ }
28
+ return existing;
29
+ }
30
+
31
+ function upsertNamedEntry(
32
+ target: Map<string, NamedEntry>,
33
+ params: { id?: string | null; name?: string | null; lastSeenAt?: number },
34
+ ) {
35
+ const id = params.id?.trim();
36
+ if (!id) {
37
+ return;
38
+ }
39
+ const current = target.get(id);
40
+ const name = params.name?.trim() || current?.name;
41
+ target.set(id, {
42
+ id,
43
+ name: name || undefined,
44
+ lastSeenAt: params.lastSeenAt ?? Date.now(),
45
+ });
46
+ }
47
+
48
+ export function rememberGeweDirectoryObservation(params: {
49
+ accountId?: string | null;
50
+ senderId?: string | null;
51
+ senderName?: string | null;
52
+ groupId?: string | null;
53
+ groupName?: string | null;
54
+ timestamp?: number;
55
+ }) {
56
+ const cache = resolveAccountCache(params.accountId);
57
+ const lastSeenAt = params.timestamp ?? Date.now();
58
+ upsertNamedEntry(cache.users, {
59
+ id: params.senderId,
60
+ name: params.senderName,
61
+ lastSeenAt,
62
+ });
63
+ upsertNamedEntry(cache.groups, {
64
+ id: params.groupId,
65
+ name: params.groupName,
66
+ lastSeenAt,
67
+ });
68
+ }
69
+
70
+ export function rememberGeweGroupMembers(params: {
71
+ accountId?: string | null;
72
+ groupId: string;
73
+ groupName?: string | null;
74
+ members: Array<{ id?: string | null; name?: string | null }>;
75
+ timestamp?: number;
76
+ }) {
77
+ const cache = resolveAccountCache(params.accountId);
78
+ const lastSeenAt = params.timestamp ?? Date.now();
79
+ upsertNamedEntry(cache.groups, {
80
+ id: params.groupId,
81
+ name: params.groupName,
82
+ lastSeenAt,
83
+ });
84
+ const memberMap = new Map<string, NamedEntry>();
85
+ for (const member of params.members) {
86
+ const id = member.id?.trim();
87
+ if (!id) {
88
+ continue;
89
+ }
90
+ const name = member.name?.trim() || undefined;
91
+ memberMap.set(id, {
92
+ id,
93
+ name,
94
+ lastSeenAt,
95
+ });
96
+ upsertNamedEntry(cache.users, {
97
+ id,
98
+ name,
99
+ lastSeenAt,
100
+ });
101
+ }
102
+ cache.groupMembers.set(params.groupId, memberMap);
103
+ }
104
+
105
+ export function rememberGeweUsers(params: {
106
+ accountId?: string | null;
107
+ users: Array<{ id?: string | null; name?: string | null }>;
108
+ timestamp?: number;
109
+ }) {
110
+ const cache = resolveAccountCache(params.accountId);
111
+ const lastSeenAt = params.timestamp ?? Date.now();
112
+ for (const user of params.users) {
113
+ upsertNamedEntry(cache.users, {
114
+ id: user.id,
115
+ name: user.name,
116
+ lastSeenAt,
117
+ });
118
+ }
119
+ }
120
+
121
+ export function listCachedGeweUsers(accountId?: string | null): NamedEntry[] {
122
+ return Array.from(resolveAccountCache(accountId).users.values());
123
+ }
124
+
125
+ export function listCachedGeweGroups(accountId?: string | null): NamedEntry[] {
126
+ return Array.from(resolveAccountCache(accountId).groups.values());
127
+ }
128
+
129
+ export function listCachedGeweGroupMembers(params: {
130
+ accountId?: string | null;
131
+ groupId: string;
132
+ }): NamedEntry[] {
133
+ return Array.from(
134
+ resolveAccountCache(params.accountId).groupMembers.get(params.groupId)?.values() ?? [],
135
+ );
136
+ }
137
+
138
+ export function resolveCachedGeweName(params: {
139
+ accountId?: string | null;
140
+ id: string;
141
+ kind: "user" | "group";
142
+ }): string | undefined {
143
+ const cache = resolveAccountCache(params.accountId);
144
+ return params.kind === "group"
145
+ ? cache.groups.get(params.id)?.name
146
+ : cache.users.get(params.id)?.name;
147
+ }
148
+
149
+ export function getGeweDirectoryCacheCounts(accountId?: string | null) {
150
+ const cache = resolveAccountCache(accountId);
151
+ const cachedGroupMemberCount = Array.from(cache.groupMembers.values()).reduce(
152
+ (sum, group) => sum + group.size,
153
+ 0,
154
+ );
155
+ return {
156
+ cachedUsersCount: cache.users.size,
157
+ cachedGroupsCount: cache.groups.size,
158
+ cachedGroupMemberCount,
159
+ };
160
+ }
161
+
162
+ export function resetGeweDirectoryCacheForTests() {
163
+ directoryCache.clear();
164
+ }
@@ -0,0 +1,27 @@
1
+ import { postGeweAccountJson } from "./api.js";
2
+ import type { ResolvedGeweAccount } from "./types.js";
3
+
4
+ export type GeweApiMethodParams<T extends Record<string, unknown>> = T & {
5
+ account: ResolvedGeweAccount;
6
+ };
7
+
8
+ export type GeweApiMethod<T extends Record<string, unknown>, R = unknown> = (
9
+ params: GeweApiMethodParams<T>,
10
+ ) => Promise<R | undefined>;
11
+
12
+ export function createGeweAccountMethod<T extends Record<string, unknown>, R = unknown>(
13
+ path: string,
14
+ ): GeweApiMethod<T, R> {
15
+ return async (params) => {
16
+ const { account, ...body } = params;
17
+ return await postGeweAccountJson<R>({
18
+ account,
19
+ path,
20
+ context: path.split("/").pop() ?? path,
21
+ body,
22
+ });
23
+ };
24
+ }
25
+
26
+ export type GeweApiObject = Record<string, unknown>;
27
+ export type GeweApiList = GeweApiObject[];
@@ -0,0 +1,242 @@
1
+ import type { AnyAgentTool, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
2
+ import { z } from "zod";
3
+
4
+ import { resolveGeweAccount } from "./accounts.js";
5
+ import { ensureGeweWriteSection } from "./config-edit.js";
6
+ import { normalizeGeweBindingConversationId, inferCurrentGeweGroupId } from "./group-binding.js";
7
+ import { normalizeAccountId, type OpenClawConfig } from "./openclaw-compat.js";
8
+ import { readGeweAllowFromStore } from "./pairing-store.js";
9
+ import type { GeweGroupConfig } from "./types.js";
10
+
11
+ const GeweManageGroupAllowlistSchema = z
12
+ .object({
13
+ mode: z.enum(["inspect", "add", "remove", "replace", "clear"]).optional().default("inspect"),
14
+ groupId: z.string().optional(),
15
+ accountId: z.string().optional(),
16
+ entries: z.array(z.string()).optional(),
17
+ })
18
+ .strict();
19
+
20
+ function jsonResult(details: Record<string, unknown>) {
21
+ return {
22
+ content: [{ type: "text" as const, text: JSON.stringify(details, null, 2) }],
23
+ details,
24
+ };
25
+ }
26
+
27
+ function dedupeEntries(values: readonly unknown[]): string[] {
28
+ const result: string[] = [];
29
+ const seen = new Set<string>();
30
+ for (const value of values) {
31
+ const normalized = String(value ?? "").trim();
32
+ if (!normalized || seen.has(normalized)) {
33
+ continue;
34
+ }
35
+ seen.add(normalized);
36
+ result.push(normalized);
37
+ }
38
+ return result;
39
+ }
40
+
41
+ function resolveToolConfig(ctx: OpenClawPluginToolContext, readConfig?: () => OpenClawConfig): OpenClawConfig {
42
+ return (readConfig?.() ?? (ctx.config as OpenClawConfig | undefined) ?? {}) as OpenClawConfig;
43
+ }
44
+
45
+ function resolveGroupId(params: {
46
+ cfg: OpenClawConfig;
47
+ accountId: string;
48
+ ctx: OpenClawPluginToolContext;
49
+ rawGroupId?: string;
50
+ }): string {
51
+ const explicit = params.rawGroupId?.trim();
52
+ if (explicit === "*") {
53
+ return "*";
54
+ }
55
+ const normalizedExplicit = normalizeGeweBindingConversationId(explicit);
56
+ if (normalizedExplicit) {
57
+ return normalizedExplicit;
58
+ }
59
+ const inferred = inferCurrentGeweGroupId({
60
+ cfg: params.cfg,
61
+ accountId: params.accountId,
62
+ sessionKey: params.ctx.sessionKey,
63
+ });
64
+ if (!inferred) {
65
+ throw new Error(
66
+ "GeWe group allowlist management requires groupId, or a current GeWe group session that can infer one.",
67
+ );
68
+ }
69
+ return inferred;
70
+ }
71
+
72
+ function readOverrideEntries(accountConfig: Record<string, unknown>, groupId: string): string[] {
73
+ const groups =
74
+ accountConfig.groups && typeof accountConfig.groups === "object" && !Array.isArray(accountConfig.groups)
75
+ ? (accountConfig.groups as Record<string, GeweGroupConfig | undefined>)
76
+ : {};
77
+ const entries = groups[groupId]?.allowFrom ?? [];
78
+ return dedupeEntries(entries);
79
+ }
80
+
81
+ function resolveEffectiveEntries(params: {
82
+ accountConfig: Record<string, unknown>;
83
+ accountId: string;
84
+ groupId: string;
85
+ pairingEntries: string[];
86
+ }): {
87
+ baseEntries: string[];
88
+ overrideEntries: string[];
89
+ effectiveEntries: string[];
90
+ } {
91
+ const baseEntries = dedupeEntries((params.accountConfig.groupAllowFrom as unknown[]) ?? []);
92
+ const overrideEntries = readOverrideEntries(params.accountConfig, params.groupId);
93
+ return {
94
+ baseEntries,
95
+ overrideEntries,
96
+ effectiveEntries: dedupeEntries([...baseEntries, ...params.pairingEntries, ...overrideEntries]),
97
+ };
98
+ }
99
+
100
+ function mutateGroupAllowlist(params: {
101
+ cfg: OpenClawConfig;
102
+ accountId: string;
103
+ groupId: string;
104
+ mode: "add" | "remove" | "replace" | "clear";
105
+ entries: string[];
106
+ }) {
107
+ const write = ensureGeweWriteSection({
108
+ cfg: params.cfg,
109
+ accountId: params.accountId,
110
+ });
111
+ const groups =
112
+ write.target.groups && typeof write.target.groups === "object" && !Array.isArray(write.target.groups)
113
+ ? (write.target.groups as Record<string, Record<string, unknown>>)
114
+ : {};
115
+ write.target.groups = groups;
116
+ const current = groups[params.groupId] ?? {};
117
+ const existing = dedupeEntries((current.allowFrom as unknown[]) ?? []);
118
+ let nextEntries = existing;
119
+
120
+ if (params.mode === "replace") {
121
+ nextEntries = dedupeEntries(params.entries);
122
+ } else if (params.mode === "clear") {
123
+ nextEntries = [];
124
+ } else {
125
+ const nextSet = new Set(existing);
126
+ for (const entry of params.entries) {
127
+ if (params.mode === "add") {
128
+ nextSet.add(entry);
129
+ } else {
130
+ nextSet.delete(entry);
131
+ }
132
+ }
133
+ nextEntries = Array.from(nextSet);
134
+ }
135
+
136
+ const nextGroup = { ...current };
137
+ if (nextEntries.length > 0) {
138
+ nextGroup.allowFrom = nextEntries;
139
+ } else {
140
+ delete nextGroup.allowFrom;
141
+ }
142
+ if (Object.keys(nextGroup).length > 0) {
143
+ groups[params.groupId] = nextGroup;
144
+ } else {
145
+ delete groups[params.groupId];
146
+ }
147
+ if (Object.keys(groups).length === 0) {
148
+ delete write.target.groups;
149
+ }
150
+ return {
151
+ nextCfg: write.nextCfg,
152
+ nextEntries,
153
+ pathLabel: `${write.pathPrefix}.groups.${params.groupId}.allowFrom`,
154
+ };
155
+ }
156
+
157
+ export function createGeweManageGroupAllowlistTool(
158
+ ctx: OpenClawPluginToolContext,
159
+ deps?: {
160
+ readConfig?: () => OpenClawConfig;
161
+ writeConfigFile?: (next: OpenClawConfig) => Promise<void>;
162
+ },
163
+ ): AnyAgentTool {
164
+ return {
165
+ name: "gewe_manage_group_allowlist",
166
+ label: "GeWe Manage Group Allowlist",
167
+ description:
168
+ "Inspect or edit a GeWe group's allowFrom override. Modes: inspect, add, remove, replace, clear.",
169
+ ownerOnly: true,
170
+ parameters: GeweManageGroupAllowlistSchema,
171
+ execute: async (_toolCallId, rawParams) => {
172
+ const params = GeweManageGroupAllowlistSchema.parse(rawParams ?? {});
173
+ const cfg = resolveToolConfig(ctx, deps?.readConfig);
174
+ const accountId = normalizeAccountId(params.accountId ?? ctx.agentAccountId ?? "default");
175
+ const groupId = resolveGroupId({
176
+ cfg,
177
+ accountId,
178
+ ctx,
179
+ rawGroupId: params.groupId,
180
+ });
181
+ const resolvedAccount = resolveGeweAccount({
182
+ cfg: cfg as never,
183
+ accountId,
184
+ });
185
+ const accountConfig = (
186
+ accountId === "default"
187
+ ? (cfg.channels?.["gewe-openclaw"] ?? {})
188
+ : ((cfg.channels?.["gewe-openclaw"] as { accounts?: Record<string, unknown> } | undefined)
189
+ ?.accounts?.[accountId] ?? {})
190
+ ) as Record<string, unknown>;
191
+
192
+ const pairingEntries = await readGeweAllowFromStore({ accountId });
193
+ const current = resolveEffectiveEntries({
194
+ accountConfig: resolvedAccount.config as Record<string, unknown>,
195
+ accountId,
196
+ groupId,
197
+ pairingEntries,
198
+ });
199
+
200
+ if (params.mode === "inspect") {
201
+ return jsonResult({
202
+ ok: true,
203
+ mode: params.mode,
204
+ accountId,
205
+ groupId,
206
+ groupPolicy: resolvedAccount.config.groupPolicy ?? "allowlist",
207
+ baseEntries: current.baseEntries,
208
+ pairingEntries,
209
+ overrideEntries: current.overrideEntries,
210
+ effectiveEntries: current.effectiveEntries,
211
+ });
212
+ }
213
+
214
+ if (!deps?.writeConfigFile) {
215
+ throw new Error("GeWe group allowlist management requires runtime config write support.");
216
+ }
217
+
218
+ const entries = dedupeEntries(params.entries ?? []);
219
+ if (params.mode !== "clear" && entries.length === 0) {
220
+ throw new Error(`GeWe ${params.mode} requires entries.`);
221
+ }
222
+
223
+ const mutated = mutateGroupAllowlist({
224
+ cfg,
225
+ accountId,
226
+ groupId,
227
+ mode: params.mode,
228
+ entries,
229
+ });
230
+ await deps.writeConfigFile(mutated.nextCfg);
231
+
232
+ return jsonResult({
233
+ ok: true,
234
+ mode: params.mode,
235
+ accountId,
236
+ groupId,
237
+ pathLabel: mutated.pathLabel,
238
+ overrideEntries: mutated.nextEntries,
239
+ });
240
+ },
241
+ };
242
+ }
@@ -0,0 +1,154 @@
1
+ import type { AnyAgentTool, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
2
+ import { z } from "zod";
3
+
4
+ import {
5
+ buildGeweDesiredBindingIdentity,
6
+ getGeweChatroomInfo,
7
+ getGeweProfile,
8
+ inferCurrentGeweGroupId,
9
+ modifyGeweChatroomRemark,
10
+ modifyGeweChatroomSelfNickname,
11
+ normalizeGeweBindingConversationId,
12
+ resolveExplicitGeweGroupBinding,
13
+ resolveGeweAccountForBindingTool,
14
+ resolveGeweAgentDisplayName,
15
+ resolveGeweBindingIdentityConfigForGroup,
16
+ resolveGeweCurrentSelfNickname,
17
+ } from "./group-binding.js";
18
+ import { normalizeAccountId, type OpenClawConfig } from "./openclaw-compat.js";
19
+
20
+ const GeweSyncGroupBindingToolSchema = z
21
+ .object({
22
+ mode: z.enum(["inspect", "dry_run", "apply"]).optional().default("inspect"),
23
+ groupId: z.string().optional(),
24
+ accountId: z.string().optional(),
25
+ syncSelfNickname: z.boolean().optional(),
26
+ syncRemark: z.boolean().optional(),
27
+ })
28
+ .strict();
29
+
30
+ function jsonResult(details: Record<string, unknown>) {
31
+ return {
32
+ content: [{ type: "text" as const, text: JSON.stringify(details, null, 2) }],
33
+ details,
34
+ };
35
+ }
36
+
37
+ export function createGeweSyncGroupBindingTool(ctx: OpenClawPluginToolContext): AnyAgentTool {
38
+ return {
39
+ name: "gewe_sync_group_binding",
40
+ label: "GeWe Sync Group Binding",
41
+ description:
42
+ "Inspect or manually sync a GeWe group binding identity. Modes: inspect, dry_run, apply.",
43
+ ownerOnly: true,
44
+ parameters: GeweSyncGroupBindingToolSchema,
45
+ execute: async (_toolCallId, rawParams) => {
46
+ const params = GeweSyncGroupBindingToolSchema.parse(rawParams ?? {});
47
+ const cfg = (ctx.config ?? {}) as OpenClawConfig;
48
+ const accountId = normalizeAccountId(params.accountId ?? ctx.agentAccountId ?? "default");
49
+ const explicitGroupId = normalizeGeweBindingConversationId(params.groupId);
50
+ const groupId =
51
+ explicitGroupId ??
52
+ inferCurrentGeweGroupId({
53
+ cfg,
54
+ accountId,
55
+ sessionKey: ctx.sessionKey,
56
+ });
57
+ if (!groupId) {
58
+ throw new Error(
59
+ "GeWe group binding sync requires groupId, or a current GeWe group session that can infer one.",
60
+ );
61
+ }
62
+
63
+ const binding = resolveExplicitGeweGroupBinding({
64
+ cfg,
65
+ accountId,
66
+ groupId,
67
+ });
68
+ if (!binding) {
69
+ throw new Error(
70
+ `GeWe group binding sync requires an explicit group binding for "${groupId}" on account "${accountId}".`,
71
+ );
72
+ }
73
+
74
+ const account = resolveGeweAccountForBindingTool({ cfg, accountId });
75
+ const identity = resolveGeweBindingIdentityConfigForGroup({
76
+ accountConfig: account.config,
77
+ groupId,
78
+ });
79
+ if (!identity.enabled) {
80
+ throw new Error(`GeWe bindingIdentity is disabled for "${groupId}".`);
81
+ }
82
+
83
+ const agentName = resolveGeweAgentDisplayName(cfg, binding.agentId);
84
+ const desired = buildGeweDesiredBindingIdentity({
85
+ identity,
86
+ agentId: binding.agentId,
87
+ agentName,
88
+ });
89
+ const profile = await getGeweProfile({ account });
90
+ const groupInfo = await getGeweChatroomInfo({ account, groupId });
91
+ const current = {
92
+ selfNickname: resolveGeweCurrentSelfNickname(groupInfo, profile.wxid),
93
+ remark: groupInfo.remark?.trim() || null,
94
+ };
95
+
96
+ const sync = {
97
+ selfNickname: params.syncSelfNickname ?? true,
98
+ remark: params.syncRemark ?? true,
99
+ };
100
+ const changes = {
101
+ selfNickname:
102
+ sync.selfNickname && (desired.selfNickname ?? null) !== (current.selfNickname ?? null),
103
+ remark: sync.remark && (desired.remark ?? null) !== (current.remark ?? null),
104
+ };
105
+ const applied = {
106
+ selfNickname: false,
107
+ remark: false,
108
+ };
109
+
110
+ if (params.mode === "apply") {
111
+ if (changes.selfNickname && desired.selfNickname) {
112
+ await modifyGeweChatroomSelfNickname({
113
+ account,
114
+ groupId,
115
+ nickName: desired.selfNickname,
116
+ });
117
+ applied.selfNickname = true;
118
+ }
119
+ if (changes.remark && desired.remark) {
120
+ await modifyGeweChatroomRemark({
121
+ account,
122
+ groupId,
123
+ remark: desired.remark,
124
+ });
125
+ applied.remark = true;
126
+ }
127
+ }
128
+
129
+ return jsonResult({
130
+ ok: true,
131
+ mode: params.mode,
132
+ accountId,
133
+ binding: {
134
+ kind: binding.kind,
135
+ groupId,
136
+ agentId: binding.agentId,
137
+ agentName,
138
+ },
139
+ sync,
140
+ current,
141
+ desired,
142
+ changes,
143
+ applied,
144
+ profile: {
145
+ wxid: profile.wxid,
146
+ },
147
+ chatroom: {
148
+ chatroomId: groupInfo.chatroomId,
149
+ nickName: groupInfo.nickName ?? null,
150
+ },
151
+ });
152
+ },
153
+ };
154
+ }