@josephyan/qingflow-app-builder-mcp 0.1.0-beta.10

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 (55) hide show
  1. package/README.md +21 -0
  2. package/docs/local-agent-install.md +228 -0
  3. package/entry_point.py +13 -0
  4. package/npm/bin/qingflow-app-builder-mcp.mjs +7 -0
  5. package/npm/lib/runtime.mjs +146 -0
  6. package/npm/scripts/postinstall.mjs +12 -0
  7. package/package.json +33 -0
  8. package/pyproject.toml +64 -0
  9. package/qingflow-app-builder-mcp +15 -0
  10. package/src/qingflow_mcp/__init__.py +5 -0
  11. package/src/qingflow_mcp/__main__.py +5 -0
  12. package/src/qingflow_mcp/backend_client.py +336 -0
  13. package/src/qingflow_mcp/config.py +182 -0
  14. package/src/qingflow_mcp/errors.py +66 -0
  15. package/src/qingflow_mcp/json_types.py +18 -0
  16. package/src/qingflow_mcp/list_type_labels.py +52 -0
  17. package/src/qingflow_mcp/server.py +70 -0
  18. package/src/qingflow_mcp/server_app_builder.py +352 -0
  19. package/src/qingflow_mcp/server_app_user.py +334 -0
  20. package/src/qingflow_mcp/session_store.py +249 -0
  21. package/src/qingflow_mcp/solution/__init__.py +6 -0
  22. package/src/qingflow_mcp/solution/build_assembly_store.py +137 -0
  23. package/src/qingflow_mcp/solution/compiler/__init__.py +265 -0
  24. package/src/qingflow_mcp/solution/compiler/chart_compiler.py +96 -0
  25. package/src/qingflow_mcp/solution/compiler/form_compiler.py +456 -0
  26. package/src/qingflow_mcp/solution/compiler/icon_utils.py +113 -0
  27. package/src/qingflow_mcp/solution/compiler/navigation_compiler.py +57 -0
  28. package/src/qingflow_mcp/solution/compiler/package_compiler.py +19 -0
  29. package/src/qingflow_mcp/solution/compiler/portal_compiler.py +60 -0
  30. package/src/qingflow_mcp/solution/compiler/view_compiler.py +51 -0
  31. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +134 -0
  32. package/src/qingflow_mcp/solution/design_session.py +222 -0
  33. package/src/qingflow_mcp/solution/design_store.py +100 -0
  34. package/src/qingflow_mcp/solution/executor.py +2065 -0
  35. package/src/qingflow_mcp/solution/normalizer.py +23 -0
  36. package/src/qingflow_mcp/solution/run_store.py +221 -0
  37. package/src/qingflow_mcp/solution/spec_models.py +853 -0
  38. package/src/qingflow_mcp/tools/__init__.py +1 -0
  39. package/src/qingflow_mcp/tools/app_tools.py +406 -0
  40. package/src/qingflow_mcp/tools/approval_tools.py +498 -0
  41. package/src/qingflow_mcp/tools/auth_tools.py +514 -0
  42. package/src/qingflow_mcp/tools/base.py +81 -0
  43. package/src/qingflow_mcp/tools/directory_tools.py +476 -0
  44. package/src/qingflow_mcp/tools/file_tools.py +375 -0
  45. package/src/qingflow_mcp/tools/navigation_tools.py +177 -0
  46. package/src/qingflow_mcp/tools/package_tools.py +198 -0
  47. package/src/qingflow_mcp/tools/portal_tools.py +100 -0
  48. package/src/qingflow_mcp/tools/qingbi_report_tools.py +235 -0
  49. package/src/qingflow_mcp/tools/record_tools.py +4307 -0
  50. package/src/qingflow_mcp/tools/role_tools.py +94 -0
  51. package/src/qingflow_mcp/tools/solution_tools.py +2684 -0
  52. package/src/qingflow_mcp/tools/task_tools.py +692 -0
  53. package/src/qingflow_mcp/tools/view_tools.py +280 -0
  54. package/src/qingflow_mcp/tools/workflow_tools.py +238 -0
  55. package/src/qingflow_mcp/tools/workspace_tools.py +170 -0
@@ -0,0 +1,692 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from mcp.server.fastmcp import FastMCP
6
+
7
+ from ..config import DEFAULT_PROFILE
8
+ from ..errors import QingflowApiError, raise_tool_error
9
+ from ..list_type_labels import get_record_list_type_label, get_task_type_label
10
+ from .base import ToolBase
11
+
12
+
13
+ class TaskTools(ToolBase):
14
+ """任务中心(待办/已办)相关工具
15
+
16
+ 提供对工作流任务的管理功能,包括:
17
+ - 查询待办/已办列表
18
+ - 查看任务统计
19
+ - 标记已读/催办
20
+ - 查询节点和表单分组信息
21
+
22
+ 消息类型 (type):
23
+ - 1: 待办 (TODO) - 需要当前用户处理的任务
24
+ - 2: 我发起的 (INITIATED) - 当前用户发起的流程
25
+ - 3: 抄送 (CC) - 抄送给当前用户的任务
26
+ - 5: 已办 (DONE) - 当前用户已处理的任务
27
+
28
+ 流程状态 (process_status):
29
+ - 1: 全部
30
+ - 2: 流程中
31
+ - 3: 已通过
32
+ - 4: 已拒绝
33
+ - 5: 待完善
34
+ - 6: 催办
35
+ - 7: 超时
36
+ - 8: 即将超时
37
+ - 9: 未读
38
+ - 10: 流程结束
39
+ """
40
+
41
+ def register(self, mcp: FastMCP) -> None:
42
+ @mcp.tool()
43
+ def task_list(
44
+ profile: str = DEFAULT_PROFILE,
45
+ type: int = 1,
46
+ process_status: int = 1,
47
+ app_key: str | None = None,
48
+ node_id: int | None = None,
49
+ search_key: str | None = None,
50
+ page_num: int = 1,
51
+ page_size: int = 20,
52
+ create_time_asc: bool | None = None,
53
+ ) -> dict[str, Any]:
54
+ """查询任务列表(待办/已办/我发起的/抄送)
55
+
56
+ Args:
57
+ profile: 配置文件名
58
+ type: 消息类型 (1=待办, 2=我发起的, 3=抄送, 5=已办)
59
+ process_status: 流程状态 (1=全部, 2=流程中, 3=已通过, 4=已拒绝, 5=待完善, 6=催办, 7=超时, 8=即将超时, 9=未读)
60
+ app_key: 应用key(可选,用于筛选特定应用)
61
+ node_id: 节点ID(可选,用于筛选特定节点)
62
+ search_key: 搜索关键词
63
+ page_num: 页码,从1开始
64
+ page_size: 每页数量
65
+ create_time_asc: 是否按创建时间升序(None表示默认排序)
66
+ """
67
+ return self.task_list(
68
+ profile=profile,
69
+ type=type,
70
+ process_status=process_status,
71
+ app_key=app_key,
72
+ node_id=node_id,
73
+ search_key=search_key,
74
+ page_num=page_num,
75
+ page_size=page_size,
76
+ create_time_asc=create_time_asc,
77
+ )
78
+
79
+ @mcp.tool()
80
+ def task_list_grouped(
81
+ profile: str = DEFAULT_PROFILE,
82
+ type: int = 1,
83
+ process_status: int = 1,
84
+ app_key: str | None = None,
85
+ node_id: int | None = None,
86
+ search_key: str | None = None,
87
+ page_num: int = 1,
88
+ page_size: int = 20,
89
+ ) -> dict[str, Any]:
90
+ """查询任务列表(带分组信息)
91
+
92
+ 返回按表单分组的任务列表,适用于需要分组展示的场景。
93
+
94
+ Args:
95
+ profile: 配置文件名
96
+ type: 消息类型 (1=待办, 2=我发起的, 3=抄送, 5=已办)
97
+ process_status: 流程状态 (1=全部, 2=流程中, 3=已通过, 4=已拒绝, 5=待完善, 6=催办, 7=超时, 8=即将超时, 9=未读)
98
+ app_key: 应用key(可选,用于筛选特定应用)
99
+ node_id: 节点ID(可选,用于筛选特定节点)
100
+ search_key: 搜索关键词
101
+ page_num: 页码,从1开始
102
+ page_size: 每页数量
103
+ """
104
+ return self.task_list_grouped(
105
+ profile=profile,
106
+ type=type,
107
+ process_status=process_status,
108
+ app_key=app_key,
109
+ node_id=node_id,
110
+ search_key=search_key,
111
+ page_num=page_num,
112
+ page_size=page_size,
113
+ )
114
+
115
+ @mcp.tool()
116
+ def task_statistics(
117
+ profile: str = DEFAULT_PROFILE,
118
+ app_key: str | None = None,
119
+ ) -> dict[str, Any]:
120
+ """查询任务中心统计信息
121
+
122
+ 获取当前用户的任务统计数量,包括:
123
+ - 待办数量
124
+ - 超时数量
125
+ - 即将超时数量
126
+ - 催办数量
127
+ - 抄送未读数量
128
+ - 我发起的流程中数量
129
+
130
+ Args:
131
+ profile: 配置文件名
132
+ app_key: 应用key(可选,用于统计特定应用)
133
+ """
134
+ return self.task_statistics(profile=profile, app_key=app_key)
135
+
136
+ @mcp.tool()
137
+ def task_workflow_nodes(
138
+ profile: str = DEFAULT_PROFILE,
139
+ type: int = 1,
140
+ status: str | None = None,
141
+ app_key_list: list[str] | None = None,
142
+ search_key: str | None = None,
143
+ page_num: int = 1,
144
+ page_size: int = 20,
145
+ ) -> dict[str, Any]:
146
+ """查询流程节点列表
147
+
148
+ 获取工作流节点信息,可用于了解当前有哪些流程节点。
149
+
150
+ Args:
151
+ profile: 配置文件名
152
+ type: 消息类型 (1=待办, 2=我发起的, 3=抄送, 5=已办)
153
+ status: 流程状态
154
+ app_key_list: 应用key列表(用于筛选特定应用)
155
+ search_key: 节点名称搜索关键词
156
+ page_num: 页码
157
+ page_size: 每页数量
158
+ """
159
+ return self.task_workflow_nodes(
160
+ profile=profile,
161
+ type=type,
162
+ status=status,
163
+ app_key_list=app_key_list,
164
+ search_key=search_key,
165
+ page_num=page_num,
166
+ page_size=page_size,
167
+ )
168
+
169
+ @mcp.tool()
170
+ def task_node_statistics(
171
+ profile: str = DEFAULT_PROFILE,
172
+ app_key: str = "",
173
+ type: int = 1,
174
+ search_key: str | None = None,
175
+ ) -> dict[str, Any]:
176
+ """查询表单下节点的分组统计信息
177
+
178
+ 获取指定应用下各节点的任务数量统计。
179
+
180
+ Args:
181
+ profile: 配置文件名
182
+ app_key: 应用key
183
+ type: 消息类型 (1=待办, 2=我发起的, 3=抄送, 5=已办)
184
+ search_key: 节点名称搜索关键词
185
+ """
186
+ return self.task_node_statistics(
187
+ profile=profile,
188
+ app_key=app_key,
189
+ type=type,
190
+ search_key=search_key,
191
+ )
192
+
193
+ @mcp.tool()
194
+ def task_worksheet_statistics(
195
+ profile: str = DEFAULT_PROFILE,
196
+ type: int = 1,
197
+ worksheet_name: str | None = None,
198
+ page_num: int = 1,
199
+ page_size: int = 20,
200
+ ) -> dict[str, Any]:
201
+ """查询表单分组统计信息
202
+
203
+ 获取各表单的任务数量统计。
204
+
205
+ Args:
206
+ profile: 配置文件名
207
+ type: 消息类型 (1=待办, 2=我发起的, 3=抄送, 5=已办)
208
+ worksheet_name: 表单名称搜索关键词
209
+ page_num: 页码
210
+ page_size: 每页数量
211
+ """
212
+ return self.task_worksheet_statistics(
213
+ profile=profile,
214
+ type=type,
215
+ worksheet_name=worksheet_name,
216
+ page_num=page_num,
217
+ page_size=page_size,
218
+ )
219
+
220
+ @mcp.tool()
221
+ def task_mark_read(
222
+ profile: str = DEFAULT_PROFILE,
223
+ app_key: str = "",
224
+ id: int = 0,
225
+ type: int = 1,
226
+ ) -> dict[str, Any]:
227
+ """标记任务为已读
228
+
229
+ Args:
230
+ profile: 配置文件名
231
+ app_key: 应用key
232
+ id: 任务ID
233
+ type: 消息类型
234
+ """
235
+ return self.task_mark_read(profile=profile, app_key=app_key, id=id, type=type)
236
+
237
+ @mcp.tool()
238
+ def task_mark_all_cc_read(
239
+ profile: str = DEFAULT_PROFILE,
240
+ type: int = 3,
241
+ process_status: int = 1,
242
+ ) -> dict[str, Any]:
243
+ """标记所有抄送为已读
244
+
245
+ Args:
246
+ profile: 配置文件名
247
+ type: 消息类型(默认为3=抄送)
248
+ process_status: 流程状态
249
+ """
250
+ return self.task_mark_all_cc_read(
251
+ profile=profile,
252
+ type=type,
253
+ process_status=process_status,
254
+ )
255
+
256
+ @mcp.tool()
257
+ def task_urge(
258
+ profile: str = DEFAULT_PROFILE,
259
+ app_key: str = "",
260
+ row_record_id: int = 0,
261
+ ) -> dict[str, Any]:
262
+ """催办任务
263
+
264
+ 对指定记录发起催办,提醒处理人尽快处理。
265
+
266
+ Args:
267
+ profile: 配置文件名
268
+ app_key: 应用key
269
+ row_record_id: 记录ID(原applyId)
270
+ """
271
+ return self.task_urge(profile=profile, app_key=app_key, row_record_id=row_record_id)
272
+
273
+ @mcp.tool()
274
+ def task_group_detail(
275
+ profile: str = DEFAULT_PROFILE,
276
+ app_key: str = "",
277
+ group_id: int = 0,
278
+ ) -> dict[str, Any]:
279
+ """查询分组详情
280
+
281
+ 获取指定分组的详细信息。
282
+
283
+ Args:
284
+ profile: 配置文件名
285
+ app_key: 应用key
286
+ group_id: 分组ID
287
+ """
288
+ return self.task_group_detail(profile=profile, app_key=app_key, group_id=group_id)
289
+
290
+ @mcp.tool()
291
+ def task_batch_processing_amount(
292
+ profile: str = DEFAULT_PROFILE,
293
+ app_key: str = "",
294
+ list_type: int = 0,
295
+ task_center_filter: dict[str, Any] | None = None,
296
+ ) -> dict[str, Any]:
297
+ """查询批量处理数量
298
+
299
+ 获取符合筛选条件的任务数量,用于批量操作前的数量确认。
300
+
301
+ Args:
302
+ profile: 配置文件名
303
+ app_key: 应用key
304
+ list_type: 操作类型
305
+ task_center_filter: 筛选条件
306
+ """
307
+ return self.task_batch_processing_amount(
308
+ profile=profile,
309
+ app_key=app_key,
310
+ list_type=list_type,
311
+ task_center_filter=task_center_filter,
312
+ )
313
+
314
+ def task_list(
315
+ self,
316
+ *,
317
+ profile: str,
318
+ type: int,
319
+ process_status: int,
320
+ app_key: str | None,
321
+ node_id: int | None,
322
+ search_key: str | None,
323
+ page_num: int,
324
+ page_size: int,
325
+ create_time_asc: bool | None,
326
+ ) -> dict[str, Any]:
327
+ self._validate_type(type)
328
+ self._validate_process_status(process_status)
329
+
330
+ def runner(session_profile, context):
331
+ payload: dict[str, Any] = {
332
+ "type": type,
333
+ "processStatus": process_status,
334
+ "pageNum": page_num,
335
+ "pageSize": page_size,
336
+ }
337
+ if app_key is not None:
338
+ payload["appKey"] = app_key
339
+ if node_id is not None:
340
+ payload["nodeId"] = node_id
341
+ if search_key:
342
+ payload["searchKey"] = search_key
343
+ if create_time_asc is not None:
344
+ payload["createTimeAsc"] = create_time_asc
345
+
346
+ result = self.backend.request("POST", context, "/task/dynamic/page", json_body=payload)
347
+ return {
348
+ "profile": profile,
349
+ "ws_id": session_profile.selected_ws_id,
350
+ "type": type,
351
+ "type_label": get_task_type_label(type),
352
+ "list_type_label": get_task_type_label(type),
353
+ "process_status": process_status,
354
+ "page": result,
355
+ }
356
+
357
+ return self._run(profile, runner)
358
+
359
+ def task_list_grouped(
360
+ self,
361
+ *,
362
+ profile: str,
363
+ type: int,
364
+ process_status: int,
365
+ app_key: str | None,
366
+ node_id: int | None,
367
+ search_key: str | None,
368
+ page_num: int,
369
+ page_size: int,
370
+ ) -> dict[str, Any]:
371
+ self._validate_type(type)
372
+ self._validate_process_status(process_status)
373
+
374
+ def runner(session_profile, context):
375
+ payload: dict[str, Any] = {
376
+ "type": type,
377
+ "processStatus": process_status,
378
+ "pageNum": page_num,
379
+ "pageSize": page_size,
380
+ }
381
+ if app_key is not None:
382
+ payload["appKey"] = app_key
383
+ if node_id is not None:
384
+ payload["nodeId"] = node_id
385
+ if search_key:
386
+ payload["searchKey"] = search_key
387
+
388
+ result = self.backend.request("POST", context, "/task/dynamic/page/group", json_body=payload)
389
+ return {
390
+ "profile": profile,
391
+ "ws_id": session_profile.selected_ws_id,
392
+ "type": type,
393
+ "type_label": get_task_type_label(type),
394
+ "list_type_label": get_task_type_label(type),
395
+ "process_status": process_status,
396
+ "page": result,
397
+ }
398
+
399
+ return self._run(profile, runner)
400
+
401
+ def task_statistics(
402
+ self,
403
+ *,
404
+ profile: str,
405
+ app_key: str | None,
406
+ ) -> dict[str, Any]:
407
+ def runner(session_profile, context):
408
+ params: dict[str, Any] = {}
409
+ if app_key:
410
+ params["appKey"] = app_key
411
+
412
+ result = self.backend.request("GET", context, "/task/dynamic/statics", params=params)
413
+ return {
414
+ "profile": profile,
415
+ "ws_id": session_profile.selected_ws_id,
416
+ "statistics": result,
417
+ }
418
+
419
+ return self._run(profile, runner)
420
+
421
+ def task_workflow_nodes(
422
+ self,
423
+ *,
424
+ profile: str,
425
+ type: int,
426
+ status: str | None,
427
+ app_key_list: list[str] | None,
428
+ search_key: str | None,
429
+ page_num: int,
430
+ page_size: int,
431
+ ) -> dict[str, Any]:
432
+ self._validate_type(type)
433
+
434
+ def runner(session_profile, context):
435
+ params: dict[str, Any] = {
436
+ "type": type,
437
+ "pageNum": page_num,
438
+ "pageSize": page_size,
439
+ }
440
+ if status is not None:
441
+ params["status"] = status
442
+ if app_key_list:
443
+ params["appKeyList"] = app_key_list
444
+ if search_key:
445
+ params["searchKey"] = search_key
446
+
447
+ result = self.backend.request("GET", context, "/task/dynamic/workflow/nodes", params=params)
448
+ return {
449
+ "profile": profile,
450
+ "ws_id": session_profile.selected_ws_id,
451
+ "type": type,
452
+ "type_label": get_task_type_label(type),
453
+ "list_type_label": get_task_type_label(type),
454
+ "page": result,
455
+ }
456
+
457
+ return self._run(profile, runner)
458
+
459
+ def task_node_statistics(
460
+ self,
461
+ *,
462
+ profile: str,
463
+ app_key: str,
464
+ type: int,
465
+ search_key: str | None,
466
+ ) -> dict[str, Any]:
467
+ self._validate_type(type)
468
+ if not app_key:
469
+ raise_tool_error(QingflowApiError.config_error("app_key is required"))
470
+
471
+ def runner(session_profile, context):
472
+ params: dict[str, Any] = {
473
+ "appKey": app_key,
474
+ "type": type,
475
+ }
476
+ if search_key:
477
+ params["searchKey"] = search_key
478
+
479
+ result = self.backend.request("GET", context, "/task/dynamic/statics/node", params=params)
480
+ return {
481
+ "profile": profile,
482
+ "ws_id": session_profile.selected_ws_id,
483
+ "app_key": app_key,
484
+ "type": type,
485
+ "type_label": get_task_type_label(type),
486
+ "list_type_label": get_task_type_label(type),
487
+ "nodes": result,
488
+ }
489
+
490
+ return self._run(profile, runner)
491
+
492
+ def task_worksheet_statistics(
493
+ self,
494
+ *,
495
+ profile: str,
496
+ type: int,
497
+ worksheet_name: str | None,
498
+ page_num: int,
499
+ page_size: int,
500
+ ) -> dict[str, Any]:
501
+ self._validate_type(type)
502
+
503
+ def runner(session_profile, context):
504
+ params: dict[str, Any] = {
505
+ "type": type,
506
+ "pageNum": page_num,
507
+ "pageSize": page_size,
508
+ }
509
+ if worksheet_name:
510
+ params["worksheetName"] = worksheet_name
511
+
512
+ result = self.backend.request("GET", context, "/task/dynamic/statics/worksheet", params=params)
513
+ return {
514
+ "profile": profile,
515
+ "ws_id": session_profile.selected_ws_id,
516
+ "type": type,
517
+ "type_label": get_task_type_label(type),
518
+ "list_type_label": get_task_type_label(type),
519
+ "page": result,
520
+ }
521
+
522
+ return self._run(profile, runner)
523
+
524
+ def task_mark_read(
525
+ self,
526
+ *,
527
+ profile: str,
528
+ app_key: str,
529
+ id: int,
530
+ type: int,
531
+ ) -> dict[str, Any]:
532
+ if not app_key:
533
+ raise_tool_error(QingflowApiError.config_error("app_key is required"))
534
+ if id <= 0:
535
+ raise_tool_error(QingflowApiError.config_error("id must be positive"))
536
+ self._validate_type(type)
537
+
538
+ def runner(session_profile, context):
539
+ result = self.backend.request(
540
+ "POST",
541
+ context,
542
+ f"/task/dynamic/{app_key}/{id}/read/{type}",
543
+ )
544
+ return {
545
+ "profile": profile,
546
+ "ws_id": session_profile.selected_ws_id,
547
+ "app_key": app_key,
548
+ "id": id,
549
+ "type": type,
550
+ "type_label": get_task_type_label(type),
551
+ "list_type_label": get_task_type_label(type),
552
+ "result": result,
553
+ }
554
+
555
+ return self._run(profile, runner)
556
+
557
+ def task_mark_all_cc_read(
558
+ self,
559
+ *,
560
+ profile: str,
561
+ type: int,
562
+ process_status: int,
563
+ ) -> dict[str, Any]:
564
+ self._validate_type(type)
565
+ self._validate_process_status(process_status)
566
+
567
+ def runner(session_profile, context):
568
+ payload: dict[str, Any] = {
569
+ "type": type,
570
+ "processStatus": process_status,
571
+ }
572
+ result = self.backend.request("POST", context, "/task/dynamic/cc/readAll", json_body=payload)
573
+ return {
574
+ "profile": profile,
575
+ "ws_id": session_profile.selected_ws_id,
576
+ "type": type,
577
+ "type_label": get_task_type_label(type),
578
+ "list_type_label": get_task_type_label(type),
579
+ "process_status": process_status,
580
+ "result": result,
581
+ }
582
+
583
+ return self._run(profile, runner)
584
+
585
+ def task_urge(
586
+ self,
587
+ *,
588
+ profile: str,
589
+ app_key: str,
590
+ row_record_id: int,
591
+ ) -> dict[str, Any]:
592
+ if not app_key:
593
+ raise_tool_error(QingflowApiError.config_error("app_key is required"))
594
+ if row_record_id <= 0:
595
+ raise_tool_error(QingflowApiError.config_error("row_record_id must be positive"))
596
+
597
+ def runner(session_profile, context):
598
+ result = self.backend.request(
599
+ "POST",
600
+ context,
601
+ f"/task/dynamic/{app_key}/{row_record_id}/urge",
602
+ )
603
+ return {
604
+ "profile": profile,
605
+ "ws_id": session_profile.selected_ws_id,
606
+ "app_key": app_key,
607
+ "row_record_id": row_record_id,
608
+ "result": result,
609
+ }
610
+
611
+ return self._run(profile, runner)
612
+
613
+ def task_group_detail(
614
+ self,
615
+ *,
616
+ profile: str,
617
+ app_key: str,
618
+ group_id: int,
619
+ ) -> dict[str, Any]:
620
+ if not app_key:
621
+ raise_tool_error(QingflowApiError.config_error("app_key is required"))
622
+ if group_id <= 0:
623
+ raise_tool_error(QingflowApiError.config_error("group_id must be positive"))
624
+
625
+ def runner(session_profile, context):
626
+ result = self.backend.request(
627
+ "GET",
628
+ context,
629
+ f"/task/dynamic/app/{app_key}/group/{group_id}/detail",
630
+ )
631
+ return {
632
+ "profile": profile,
633
+ "ws_id": session_profile.selected_ws_id,
634
+ "app_key": app_key,
635
+ "group_id": group_id,
636
+ "detail": result,
637
+ }
638
+
639
+ return self._run(profile, runner)
640
+
641
+ def task_batch_processing_amount(
642
+ self,
643
+ *,
644
+ profile: str,
645
+ app_key: str,
646
+ list_type: int,
647
+ task_center_filter: dict[str, Any] | None,
648
+ ) -> dict[str, Any]:
649
+ if not app_key:
650
+ raise_tool_error(QingflowApiError.config_error("app_key is required"))
651
+
652
+ def runner(session_profile, context):
653
+ payload: dict[str, Any] = {
654
+ "listType": list_type,
655
+ }
656
+ if task_center_filter is not None:
657
+ payload["taskCenterFilter"] = task_center_filter
658
+
659
+ result = self.backend.request(
660
+ "POST",
661
+ context,
662
+ f"/task/app/{app_key}/batchProcessingAmount",
663
+ json_body=payload,
664
+ )
665
+ return {
666
+ "profile": profile,
667
+ "ws_id": session_profile.selected_ws_id,
668
+ "app_key": app_key,
669
+ "list_type": list_type,
670
+ "list_type_label": get_record_list_type_label(list_type),
671
+ "amount": result,
672
+ }
673
+
674
+ return self._run(profile, runner)
675
+
676
+ def _validate_type(self, type: int) -> None:
677
+ valid_types = [1, 2, 3, 5] # TODO, INITIATED, CC, DONE
678
+ if type not in valid_types:
679
+ raise_tool_error(
680
+ QingflowApiError.config_error(
681
+ f"Invalid type: {type}. Must be one of {valid_types} (1=待办, 2=我发起的, 3=抄送, 5=已办)"
682
+ )
683
+ )
684
+
685
+ def _validate_process_status(self, process_status: int) -> None:
686
+ valid_statuses = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
687
+ if process_status not in valid_statuses:
688
+ raise_tool_error(
689
+ QingflowApiError.config_error(
690
+ f"Invalid process_status: {process_status}. Must be one of {valid_statuses}"
691
+ )
692
+ )