@yanhaidao/wecom 2.3.150 → 2.3.180

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 (43) hide show
  1. package/README.md +238 -385
  2. package/SKILLS_CAL.md +895 -0
  3. package/SKILLS_DOC.md +2136 -0
  4. package/changelog/v2.3.16.md +11 -0
  5. package/changelog/v2.3.18.md +22 -0
  6. package/index.ts +39 -3
  7. package/package.json +2 -3
  8. package/src/agent/handler.event-filter.test.ts +11 -0
  9. package/src/agent/handler.ts +732 -643
  10. package/src/app/account-runtime.ts +46 -20
  11. package/src/app/index.ts +19 -1
  12. package/src/capability/calendar/SKILLS_CHECKLIST.md +251 -0
  13. package/src/capability/calendar/client.ts +815 -0
  14. package/src/capability/calendar/index.ts +3 -0
  15. package/src/capability/calendar/schema.ts +417 -0
  16. package/src/capability/calendar/tool.ts +417 -0
  17. package/src/capability/calendar/types.ts +309 -0
  18. package/src/capability/doc/client.ts +567 -62
  19. package/src/capability/doc/schema.ts +419 -318
  20. package/src/capability/doc/tool.ts +1510 -1178
  21. package/src/capability/doc/types.ts +130 -14
  22. package/src/capability/mcp/index.ts +10 -0
  23. package/src/capability/mcp/schema.ts +107 -0
  24. package/src/capability/mcp/tool.ts +170 -0
  25. package/src/capability/mcp/transport.ts +394 -0
  26. package/src/channel.ts +70 -28
  27. package/src/config/schema.ts +71 -102
  28. package/src/outbound.test.ts +91 -14
  29. package/src/outbound.ts +143 -30
  30. package/src/runtime/reply-orchestrator.test.ts +35 -2
  31. package/src/runtime/reply-orchestrator.ts +14 -2
  32. package/src/runtime/session-manager.ts +20 -6
  33. package/src/runtime/source-registry.ts +165 -0
  34. package/src/target.ts +7 -4
  35. package/src/transport/bot-ws/inbound.test.ts +46 -0
  36. package/src/transport/bot-ws/inbound.ts +23 -5
  37. package/src/transport/bot-ws/media.ts +269 -0
  38. package/src/transport/bot-ws/reply.test.ts +85 -17
  39. package/src/transport/bot-ws/reply.ts +109 -21
  40. package/src/transport/bot-ws/sdk-adapter.test.ts +64 -1
  41. package/src/transport/bot-ws/sdk-adapter.ts +88 -12
  42. package/.claude/settings.local.json +0 -11
  43. package/docs/update-content-fix.md +0 -135
@@ -1,8 +1,11 @@
1
1
  import { formatErrorMessage, type OpenClawConfig, type PluginRuntime } from "openclaw/plugin-sdk";
2
-
3
- import { InMemoryRuntimeStore } from "../store/memory-store.js";
4
- import { WecomMediaService } from "../shared/media-service.js";
2
+ import type { ResolvedRuntimeAccount } from "../config/runtime-config.js";
3
+ import { WecomAuditLog } from "../observability/audit-log.js";
4
+ import { WecomStatusRegistry } from "../observability/status-registry.js";
5
5
  import { summarizeTransportSessions } from "../observability/transport-session-view.js";
6
+ import { dispatchInboundEvent } from "../runtime/dispatcher.js";
7
+ import { WecomMediaService } from "../shared/media-service.js";
8
+ import { InMemoryRuntimeStore } from "../store/memory-store.js";
6
9
  import type {
7
10
  AccountRuntimeStatusSnapshot,
8
11
  ReplyHandle,
@@ -14,10 +17,6 @@ import type {
14
17
  WecomRuntimeHealth,
15
18
  WecomTransportKind,
16
19
  } from "../types/index.js";
17
- import type { ResolvedRuntimeAccount } from "../config/runtime-config.js";
18
- import { dispatchInboundEvent } from "../runtime/dispatcher.js";
19
- import { WecomAuditLog } from "../observability/audit-log.js";
20
- import { WecomStatusRegistry } from "../observability/status-registry.js";
21
20
 
22
21
  export class WecomAccountRuntime {
23
22
  readonly store = new InMemoryRuntimeStore();
@@ -60,23 +59,37 @@ export class WecomAccountRuntime {
60
59
  }
61
60
 
62
61
  async handleEvent(event: UnifiedInboundEvent, replyHandle: ReplyHandle): Promise<void> {
62
+ const dispatchStartedAt = Date.now();
63
63
  this.runtimeStatus.lastInboundAt = Date.now();
64
64
  this.runtimeStatus.recentInboundSummary = `${event.transport} ${event.inboundKind} ${event.messageId}`;
65
65
  this.log.info?.(
66
66
  `[wecom-runtime] inbound account=${event.accountId} transport=${event.transport} kind=${event.inboundKind} messageId=${event.messageId} peer=${event.conversation.peerKind}:${event.conversation.peerId}`,
67
67
  );
68
+ this.log.info?.(
69
+ `[wecom-runtime] dispatch-start account=${event.accountId} transport=${event.transport} kind=${event.inboundKind} messageId=${event.messageId}`,
70
+ );
68
71
  this.emitStatus();
69
72
 
70
73
  const trackedReplyHandle: ReplyHandle = {
71
74
  context: replyHandle.context,
72
75
  deliver: async (payload: ReplyPayload, info) => {
76
+ const deliverStartedAt = Date.now();
77
+ const textLen = payload.text?.trim().length ?? 0;
78
+ const mediaCount = (payload.mediaUrls?.length ?? 0) + (payload.mediaUrl ? 1 : 0);
79
+ this.log.info?.(
80
+ `[wecom-runtime] deliver-start account=${event.accountId} transport=${replyHandle.context.transport} kind=${info.kind} messageId=${event.messageId} textLen=${textLen} mediaCount=${mediaCount} reasoning=${String(payload.isReasoning === true)}`,
81
+ );
73
82
  await replyHandle.deliver(payload, info);
74
83
  this.runtimeStatus.lastOutboundAt = Date.now();
75
- const outboundSummary = payload.text?.trim() || payload.mediaUrl || payload.mediaUrls?.[0] || info.kind;
84
+ const outboundSummary =
85
+ payload.text?.trim() || payload.mediaUrl || payload.mediaUrls?.[0] || info.kind;
76
86
  this.runtimeStatus.recentOutboundSummary = `${replyHandle.context.transport} ${outboundSummary.slice(0, 120)}`;
77
87
  this.log.info?.(
78
88
  `[wecom-runtime] outbound account=${event.accountId} transport=${replyHandle.context.transport} kind=${info.kind} messageId=${event.messageId} summary=${JSON.stringify(this.runtimeStatus.recentOutboundSummary)}`,
79
89
  );
90
+ this.log.info?.(
91
+ `[wecom-runtime] deliver-done account=${event.accountId} transport=${replyHandle.context.transport} kind=${info.kind} messageId=${event.messageId} durationMs=${Date.now() - deliverStartedAt}`,
92
+ );
80
93
  this.emitStatus();
81
94
  },
82
95
  fail: async (error: unknown) => {
@@ -96,15 +109,25 @@ export class WecomAccountRuntime {
96
109
  },
97
110
  };
98
111
 
99
- await dispatchInboundEvent({
100
- core: this.core,
101
- cfg: this.cfg,
102
- store: this.store,
103
- auditLog: this.auditLog,
104
- mediaService: this.mediaService,
105
- event,
106
- replyHandle: trackedReplyHandle,
107
- });
112
+ try {
113
+ await dispatchInboundEvent({
114
+ core: this.core,
115
+ cfg: this.cfg,
116
+ store: this.store,
117
+ auditLog: this.auditLog,
118
+ mediaService: this.mediaService,
119
+ event,
120
+ replyHandle: trackedReplyHandle,
121
+ });
122
+ this.log.info?.(
123
+ `[wecom-runtime] dispatch-done account=${event.accountId} transport=${event.transport} kind=${event.inboundKind} messageId=${event.messageId} durationMs=${Date.now() - dispatchStartedAt}`,
124
+ );
125
+ } catch (error) {
126
+ this.log.error?.(
127
+ `[wecom-runtime] dispatch-fail account=${event.accountId} transport=${event.transport} kind=${event.inboundKind} messageId=${event.messageId} durationMs=${Date.now() - dispatchStartedAt} error=${formatErrorMessage(error)}`,
128
+ );
129
+ throw error;
130
+ }
108
131
  }
109
132
 
110
133
  updateTransportSession(snapshot: TransportSessionSnapshot): void {
@@ -148,7 +171,7 @@ export class WecomAccountRuntime {
148
171
  lastDisconnectedAt: patch.lastDisconnectedAt ?? current?.lastDisconnectedAt,
149
172
  lastInboundAt: patch.lastInboundAt ?? current?.lastInboundAt,
150
173
  lastOutboundAt: patch.lastOutboundAt ?? current?.lastOutboundAt,
151
- lastError: "lastError" in patch ? patch.lastError ?? undefined : current?.lastError,
174
+ lastError: "lastError" in patch ? (patch.lastError ?? undefined) : current?.lastError,
152
175
  };
153
176
  this.updateTransportSession(next);
154
177
  }
@@ -173,7 +196,7 @@ export class WecomAccountRuntime {
173
196
  authenticated: primarySession?.authenticated,
174
197
  lastError:
175
198
  primarySession?.lastError ??
176
- (primarySession?.running ? null : this.runtimeStatus.lastError ?? null),
199
+ (primarySession?.running ? null : (this.runtimeStatus.lastError ?? null)),
177
200
  transportSessions: summarizeTransportSessions(sessions),
178
201
  };
179
202
  }
@@ -202,7 +225,10 @@ export class WecomAccountRuntime {
202
225
  this.runtimeStatus.lastErrorAt = Date.now();
203
226
  this.runtimeStatus.recentIssueCategory = params.category;
204
227
  this.runtimeStatus.recentIssueSummary = params.summary;
205
- const sink = params.category === "runtime-error" || params.category === "fallback-delivery-failed" ? this.log.error : this.log.warn;
228
+ const sink =
229
+ params.category === "runtime-error" || params.category === "fallback-delivery-failed"
230
+ ? this.log.error
231
+ : this.log.warn;
206
232
  sink?.(
207
233
  `[wecom-runtime] issue account=${this.account.accountId} transport=${params.transport} category=${params.category} messageId=${params.messageId ?? "n/a"} summary=${params.summary}`,
208
234
  );
package/src/app/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { PluginRuntime } from "openclaw/plugin-sdk";
2
-
2
+ import { clearWecomSourceAccount } from "../runtime/source-registry.js";
3
3
  import { WecomAccountRuntime } from "./account-runtime.js";
4
4
 
5
5
  let runtime: PluginRuntime | null = null;
@@ -9,6 +9,23 @@ const botWsPushHandles = new Map<string, BotWsPushHandle>();
9
9
  export type BotWsPushHandle = {
10
10
  isConnected: () => boolean;
11
11
  sendMarkdown: (chatId: string, content: string) => Promise<void>;
12
+ replyCommand: (params: {
13
+ cmd: string;
14
+ body?: Record<string, unknown>;
15
+ headers?: Record<string, string>;
16
+ }) => Promise<Record<string, unknown>>;
17
+ sendMedia: (params: {
18
+ chatId: string;
19
+ mediaUrl: string;
20
+ text?: string;
21
+ mediaLocalRoots?: readonly string[];
22
+ }) => Promise<{
23
+ ok: boolean;
24
+ messageId?: string;
25
+ rejected?: boolean;
26
+ rejectReason?: string;
27
+ error?: string;
28
+ }>;
12
29
  };
13
30
 
14
31
  export function setWecomRuntime(next: PluginRuntime): void {
@@ -50,5 +67,6 @@ export function unregisterBotWsPushHandle(accountId: string): void {
50
67
  export function unregisterAccountRuntime(accountId: string): void {
51
68
  runtimes.delete(accountId);
52
69
  botWsPushHandles.delete(accountId);
70
+ clearWecomSourceAccount(accountId);
53
71
  console.log(`[wecom-runtime] unregister account=${accountId}`);
54
72
  }
@@ -0,0 +1,251 @@
1
+ # WeCom Calendar Skills 技能清单
2
+
3
+ ## 技能列表
4
+
5
+ ### wecom_calendar - 企业微信日历工具
6
+
7
+ **技能 ID**: `wecom_calendar`
8
+ **标签**: `WeCom Calendar`
9
+ **描述**: 企业微信日历工具。支持创建/更新/删除日历和日程,管理日程参与者,获取日历详情等功能。
10
+
11
+ ## 支持的 Actions (13 个)
12
+
13
+ ### 日历管理 (4 个)
14
+
15
+ #### 1. calendar_create - 创建日历
16
+ **参数**:
17
+ - `summary` (必填): 日历标题,1-128 字符
18
+ - `color` (必填): RGB 颜色编码,如 #FF3030
19
+ - `description` (可选): 日历描述,0-512 字符
20
+ - `admins` (可选): 日历管理员 userid 列表,最多 3 人
21
+ - `set_as_default` (可选): 是否设为默认日历,0-否,1-是(第三方应用不支持)
22
+ - `shares` (可选): 通知范围成员列表,最多 2000 人
23
+ - `is_public` (可选): 是否公共日历,0-否,1-是
24
+ - `public_range` (可选): 公开范围
25
+ - `userids`: 公开的成员列表,最多 1000 个
26
+ - `partyids`: 公开的部门列表,最多 100 个
27
+ - `is_corp_calendar` (可选): 是否全员日历,0-否,1-是
28
+
29
+ **返回**: `{ ok: true, action: "calendar_create", calId: string, raw: object }`
30
+
31
+ #### 2. calendar_update - 更新日历
32
+ **参数**:
33
+ - `cal_id` (必填): 日历 ID
34
+ - `summary` (必填): 日历标题,1-128 字符
35
+ - `color` (必填): RGB 颜色编码
36
+ - `description` (可选): 日历描述,0-512 字符
37
+ - `admins` (可选): 日历管理员,最多 3 人
38
+ - `shares` (可选): 通知范围成员,最多 2000 人
39
+ - `public_range` (可选): 公开范围
40
+ - `skip_public_range` (可选): 是否不更新可订阅范围,0-否,1-是
41
+
42
+ **返回**: `{ ok: true, action: "calendar_update", calId: string, raw: object }`
43
+
44
+ #### 3. calendar_get - 获取日历详情
45
+ **参数**:
46
+ - `cal_id_list` (必填): 日历 ID 列表,一次最多 1000 条
47
+
48
+ **返回**: `{ ok: true, action: "calendar_get", calendarList: array, raw: object }`
49
+
50
+ #### 4. calendar_delete - 删除日历
51
+ **参数**:
52
+ - `cal_id` (必填): 日历 ID
53
+
54
+ **返回**: `{ ok: true, action: "calendar_delete", calId: string, raw: object }`
55
+
56
+ ### 日程管理 (7 个)
57
+
58
+ #### 5. schedule_create - 创建日程
59
+ **参数**:
60
+ - `start_time` (必填): 日程开始时间,Unix 时间戳(秒)
61
+ - `end_time` (必填): 日程结束时间,Unix 时间戳(秒)
62
+ - `is_whole_day` (可选): 是否全天日程,0-否,1-是
63
+ - `summary` (可选): 日程标题,0-128 字符
64
+ - `description` (可选): 日程描述,不多于 1000 字符
65
+ - `location` (可选): 日程地址,不多于 128 字符
66
+ - `attendees` (可选): 日程参与者列表,最多 1000 人
67
+ - `admins` (可选): 日程管理员,最多 3 人
68
+ - `reminders` (可选): 提醒相关信息
69
+ - `is_remind`: 是否需要提醒,0-否,1-是
70
+ - `is_repeat`: 是否重复日程,0-否,1-是
71
+ - `remind_before_event_secs`: 日程开始前多少秒提醒 [0,300,900,3600,86400]
72
+ - `remind_time_diffs`: 提醒时间与日程开始时间的差值数组
73
+ - `repeat_type`: 重复类型 [0-每日,1-每周,2-每月,5-每年,7-工作日]
74
+ - `repeat_until`: 重复结束时刻,Unix 时间戳
75
+ - `is_custom_repeat`: 是否自定义重复,0-否,1-是
76
+ - `repeat_interval`: 重复间隔
77
+ - `repeat_day_of_week`: 每周周几重复 [1-7]
78
+ - `repeat_day_of_month`: 每月哪几天重复 [1-31]
79
+ - `timezone`: 时区 [-12 到 +12]
80
+ - `cal_id` (可选): 日程所属日历 ID(第三方应用必须指定)
81
+
82
+ **返回**: `{ ok: true, action: "schedule_create", scheduleId: string, raw: object }`
83
+
84
+ #### 6. schedule_update - 更新日程
85
+ **参数**:
86
+ - `schedule_id` (必填): 日程 ID
87
+ - `start_time` (必填): 日程开始时间
88
+ - `end_time` (必填): 日程结束时间
89
+ - `is_whole_day` (可选): 是否全天日程
90
+ - `summary` (可选): 日程标题
91
+ - `description` (可选): 日程描述
92
+ - `location` (可选): 日程地址
93
+ - `attendees` (可选): 日程参与者
94
+ - `admins` (可选): 日程管理员
95
+ - `reminders` (可选): 提醒相关信息
96
+ - `skip_attendees` (可选): 是否不更新参与人,0-否,1-是
97
+ - `op_mode` (可选): 操作模式 [0-全部修改,1-仅修改此日程,2-修改将来的所有日程]
98
+ - `op_start_time` (可选): 操作起始时间
99
+
100
+ **返回**: `{ ok: true, action: "schedule_update", scheduleId: string, raw: object }`
101
+
102
+ #### 7. schedule_add_attendees - 新增日程参与者
103
+ **参数**:
104
+ - `schedule_id` (必填): 日程 ID
105
+ - `attendees` (必填): 日程参与者列表,累计最多 1000 人
106
+
107
+ **返回**: `{ ok: true, action: "schedule_add_attendees", scheduleId: string, raw: object }`
108
+
109
+ #### 8. schedule_del_attendees - 删除日程参与者
110
+ **参数**:
111
+ - `schedule_id` (必填): 日程 ID
112
+ - `attendees` (必填): 日程参与者列表,最多 1000 人
113
+
114
+ **返回**: `{ ok: true, action: "schedule_del_attendees", scheduleId: string, raw: object }`
115
+
116
+ #### 9. schedule_get_by_calendar - 获取日历下的日程列表
117
+ **参数**:
118
+ - `cal_id` (必填): 日历 ID
119
+ - `offset` (可选): 分页偏移量,默认 0
120
+ - `limit` (可选): 分页大小,默认 500,范围 1-1000
121
+
122
+ **返回**: `{ ok: true, action: "schedule_get_by_calendar", scheduleList: array, raw: object }`
123
+
124
+ #### 10. schedule_get - 获取日程详情
125
+ **参数**:
126
+ - `schedule_id_list` (必填): 日程 ID 列表,一次最多 1000 条
127
+
128
+ **返回**: `{ ok: true, action: "schedule_get", scheduleList: array, meetingCode: string, meetingLink: string, raw: object }`
129
+
130
+ #### 11. schedule_delete - 取消日程
131
+ **参数**:
132
+ - `schedule_id` (必填): 日程 ID
133
+ - `op_mode` (可选): 操作模式 [0-删除所有,1-仅删除此日程,2-删除本次及后续]
134
+ - `op_start_time` (可选): 操作起始时间
135
+
136
+ **返回**: `{ ok: true, action: "schedule_delete", scheduleId: string, raw: object }`
137
+
138
+ ### 默认日历管理 (2 个)
139
+
140
+ #### 12. schedule_get_system_calid - 获取默认日历本 ID
141
+ **参数**:
142
+ - `userid` (必填): 指定成员的 userid
143
+
144
+ **返回**: `{ ok: true, action: "schedule_get_system_calid", calId: string, raw: object }`
145
+
146
+ #### 13. schedule_create_in_system - 在默认日历创建日程
147
+ **参数**:
148
+ - `organizer` (必填): 日程创建者 userid
149
+ - `start_time` (必填): 日程开始时间
150
+ - `end_time` (必填): 日程结束时间
151
+ - `is_whole_day` (可选): 是否全天日程
152
+ - `summary` (可选): 日程标题
153
+ - `description` (可选): 日程描述
154
+ - `location` (可选): 日程地址
155
+ - `attendees` (可选): 日程参与者
156
+ - `reminders` (可选): 提醒相关信息
157
+
158
+ **返回**: `{ ok: true, action: "schedule_create_in_system", scheduleId: string, raw: object }`
159
+
160
+ ## 与官方文档对比验证
161
+
162
+ ### 接口完整性 ✓
163
+ - [x] 创建日历 (calendar/add) - 完整实现
164
+ - [x] 更新日历 (calendar/update) - 完整实现
165
+ - [x] 获取日历 (calendar/get) - 完整实现
166
+ - [x] 删除日历 (calendar/del) - 完整实现
167
+ - [x] 创建日程 (schedule/add) - 完整实现
168
+ - [x] 更新日程 (schedule/update) - 完整实现
169
+ - [x] 新增参与者 (schedule/add_attendees) - 完整实现
170
+ - [x] 删除参与者 (schedule/del_attendees) - 完整实现
171
+ - [x] 获取日程列表 (schedule/get_by_calendar) - 完整实现
172
+ - [x] 获取日程详情 (schedule/get) - 完整实现
173
+ - [x] 取消日程 (schedule/del) - 完整实现
174
+ - [x] 获取默认日历 ID (calendar/get_system_calid) - 完整实现
175
+ - [x] 创建默认日历日程 (schedule/add_schedule_in_system_cal) - 完整实现
176
+ - [x] 更新日程回执 (schedule/respond) - 已实现(client.ts)
177
+ - [x] 同步日程 (schedule/sync) - 已实现(client.ts)
178
+
179
+ ### 参数验证 ✓
180
+ - [x] 所有必填参数都有 required 声明
181
+ - [x] 所有参数的类型、格式、取值范围都正确
182
+ - [x] 所有接口限制都有验证(数量、字符)
183
+ - [x] 所有枚举值都正确定义
184
+
185
+ ### 返回值处理 ✓
186
+ - [x] 所有接口返回结果结构正确
187
+ - [x] 错误处理完善(HTTP 错误、JSON 错误、业务错误)
188
+ - [x] 重试机制实现(3 次)
189
+
190
+ ### 特殊功能 ✓
191
+ - [x] op_mode 操作模式支持(0/1/2)
192
+ - [x] op_start_time 重复日程操作
193
+ - [x] 提醒时间枚举值验证
194
+ - [x] 时区范围验证(-12 到 +12)
195
+ - [x] 重复日程自定义设置
196
+
197
+ ## 已知限制和注意事项
198
+
199
+ 1. **第三方应用**:不支持 `set_as_default` 参数
200
+ 2. **全员日历**:每个企业最多 20 个,不支持指定颜色
201
+ 3. **公共日历**:每人最多创建或订阅 100 个
202
+ 4. **日程数量**:每个应用每天最多 2 万个
203
+ 5. **重复日程修改**:需指定 op_mode 和 op_start_time
204
+ 6. **会议室关联**:已预约会议室的日程更新时间受限
205
+
206
+ ## 待实现功能
207
+
208
+ 1. **回调事件处理** - 需要在 webhook 中添加以下事件处理:
209
+ - delete_calendar
210
+ - modify_calendar
211
+ - modify_schedule
212
+ - delete_schedule
213
+ - respond_schedule
214
+ - add_schedule
215
+
216
+ 2. **完整实现** - client.ts 和 tool.ts 需要完善所有方法的具体实现
217
+
218
+ ## 使用示例
219
+
220
+ ```json
221
+ // 创建日历
222
+ {
223
+ "action": "calendar_create",
224
+ "summary": "团队日历",
225
+ "color": "#FF3030",
226
+ "description": "团队共享日历",
227
+ "admins": ["zhangsan", "lisi"],
228
+ "shares": [{"userid": "wangwu", "permission": 1}]
229
+ }
230
+
231
+ // 创建日程
232
+ {
233
+ "action": "schedule_create",
234
+ "start_time": 1679011200,
235
+ "end_time": 1679014800,
236
+ "summary": "项目评审会议",
237
+ "location": "10 楼会议室",
238
+ "attendees": [{"userid": "user1"}, {"userid": "user2"}],
239
+ "reminders": {
240
+ "is_remind": 1,
241
+ "remind_before_event_secs": 3600,
242
+ "timezone": 8
243
+ }
244
+ }
245
+
246
+ // 获取日程详情
247
+ {
248
+ "action": "schedule_get",
249
+ "schedule_id_list": ["schedule_id_1", "schedule_id_2"]
250
+ }
251
+ ```