@josephyan/qingflow-app-user-mcp 0.2.0-beta.20 → 0.2.0-beta.21
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/README.md +2 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-user/SKILL.md +183 -113
- package/skills/qingflow-app-user/references/data-gotchas.md +20 -30
- package/skills/qingflow-app-user/references/environments.md +1 -1
- package/skills/qingflow-app-user/references/record-patterns.md +80 -66
- package/skills/qingflow-app-user/references/workflow-usage.md +10 -8
- package/skills/qingflow-record-analysis/SKILL.md +4 -4
- package/skills/qingflow-record-analysis/agents/openai.yaml +1 -1
- package/skills/qingflow-record-analysis/references/analysis-gotchas.md +2 -2
- package/skills/qingflow-record-analysis/references/analysis-patterns.md +2 -2
- package/skills/qingflow-record-analysis/references/confidence-reporting.md +2 -2
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/server.py +6 -6
- package/src/qingflow_mcp/server_app_user.py +8 -183
- package/src/qingflow_mcp/tools/approval_tools.py +357 -75
- package/src/qingflow_mcp/tools/directory_tools.py +158 -28
- package/src/qingflow_mcp/tools/record_tools.py +619 -120
- package/src/qingflow_mcp/tools/task_tools.py +376 -225
|
@@ -9,6 +9,26 @@ from ..errors import QingflowApiError, raise_tool_error
|
|
|
9
9
|
from ..list_type_labels import get_record_list_type_label, get_task_type_label
|
|
10
10
|
from .base import ToolBase
|
|
11
11
|
|
|
12
|
+
TASK_BOX_TO_TYPE = {
|
|
13
|
+
"todo": 1,
|
|
14
|
+
"initiated": 2,
|
|
15
|
+
"cc": 3,
|
|
16
|
+
"done": 5,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
FLOW_STATUS_TO_PROCESS_STATUS = {
|
|
20
|
+
"all": 1,
|
|
21
|
+
"in_progress": 2,
|
|
22
|
+
"approved": 3,
|
|
23
|
+
"rejected": 4,
|
|
24
|
+
"pending_fix": 5,
|
|
25
|
+
"urged": 6,
|
|
26
|
+
"overdue": 7,
|
|
27
|
+
"due_soon": 8,
|
|
28
|
+
"unread": 9,
|
|
29
|
+
"ended": 10,
|
|
30
|
+
}
|
|
31
|
+
|
|
12
32
|
|
|
13
33
|
class TaskTools(ToolBase):
|
|
14
34
|
"""任务中心(待办/已办)相关工具
|
|
@@ -40,276 +60,278 @@ class TaskTools(ToolBase):
|
|
|
40
60
|
|
|
41
61
|
def register(self, mcp: FastMCP) -> None:
|
|
42
62
|
@mcp.tool()
|
|
43
|
-
def
|
|
63
|
+
def task_summary(
|
|
44
64
|
profile: str = DEFAULT_PROFILE,
|
|
45
|
-
type: int = 1,
|
|
46
|
-
process_status: int = 1,
|
|
47
65
|
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
66
|
) -> 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(
|
|
67
|
+
return self.task_summary(
|
|
68
68
|
profile=profile,
|
|
69
|
-
type=type,
|
|
70
|
-
process_status=process_status,
|
|
71
69
|
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
70
|
)
|
|
78
71
|
|
|
79
72
|
@mcp.tool()
|
|
80
|
-
def
|
|
73
|
+
def task_list(
|
|
81
74
|
profile: str = DEFAULT_PROFILE,
|
|
82
|
-
|
|
83
|
-
|
|
75
|
+
task_box: str = "todo",
|
|
76
|
+
flow_status: str = "all",
|
|
84
77
|
app_key: str | None = None,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
78
|
+
workflow_node_id: int | None = None,
|
|
79
|
+
query: str | None = None,
|
|
80
|
+
page: int = 1,
|
|
88
81
|
page_size: int = 20,
|
|
82
|
+
sort_by: str | None = None,
|
|
83
|
+
sort_direction: str = "desc",
|
|
89
84
|
) -> 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(
|
|
85
|
+
return self.task_list_public(
|
|
105
86
|
profile=profile,
|
|
106
|
-
|
|
107
|
-
|
|
87
|
+
task_box=task_box,
|
|
88
|
+
flow_status=flow_status,
|
|
108
89
|
app_key=app_key,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
90
|
+
workflow_node_id=workflow_node_id,
|
|
91
|
+
query=query,
|
|
92
|
+
page=page,
|
|
112
93
|
page_size=page_size,
|
|
94
|
+
sort_by=sort_by,
|
|
95
|
+
sort_direction=sort_direction,
|
|
113
96
|
)
|
|
114
97
|
|
|
115
98
|
@mcp.tool()
|
|
116
|
-
def
|
|
99
|
+
def task_facets(
|
|
117
100
|
profile: str = DEFAULT_PROFILE,
|
|
101
|
+
task_box: str = "todo",
|
|
102
|
+
flow_status: str = "all",
|
|
103
|
+
dimension: str = "worksheet",
|
|
118
104
|
app_key: str | None = None,
|
|
105
|
+
query: str | None = None,
|
|
106
|
+
limit: int = 50,
|
|
119
107
|
) -> 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(
|
|
108
|
+
return self.task_facets(
|
|
187
109
|
profile=profile,
|
|
110
|
+
task_box=task_box,
|
|
111
|
+
flow_status=flow_status,
|
|
112
|
+
dimension=dimension,
|
|
188
113
|
app_key=app_key,
|
|
189
|
-
|
|
190
|
-
|
|
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,
|
|
114
|
+
query=query,
|
|
115
|
+
limit=limit,
|
|
218
116
|
)
|
|
219
117
|
|
|
220
118
|
@mcp.tool()
|
|
221
119
|
def task_mark_read(
|
|
222
120
|
profile: str = DEFAULT_PROFILE,
|
|
223
121
|
app_key: str = "",
|
|
224
|
-
|
|
225
|
-
|
|
122
|
+
task_id: int = 0,
|
|
123
|
+
task_box: str = "todo",
|
|
226
124
|
) -> 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)
|
|
125
|
+
return self.task_mark_read_public(profile=profile, app_key=app_key, task_id=task_id, task_box=task_box)
|
|
236
126
|
|
|
237
127
|
@mcp.tool()
|
|
238
128
|
def task_mark_all_cc_read(
|
|
239
129
|
profile: str = DEFAULT_PROFILE,
|
|
240
|
-
|
|
241
|
-
process_status: int = 1,
|
|
130
|
+
flow_status: str = "all",
|
|
242
131
|
) -> 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
|
-
)
|
|
132
|
+
return self.task_mark_all_cc_read_public(profile=profile, flow_status=flow_status)
|
|
255
133
|
|
|
256
134
|
@mcp.tool()
|
|
257
135
|
def task_urge(
|
|
258
136
|
profile: str = DEFAULT_PROFILE,
|
|
259
137
|
app_key: str = "",
|
|
260
|
-
|
|
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,
|
|
138
|
+
record_id: int = 0,
|
|
296
139
|
) -> dict[str, Any]:
|
|
297
|
-
|
|
140
|
+
return self.task_urge_public(profile=profile, app_key=app_key, record_id=record_id)
|
|
298
141
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
142
|
+
def task_summary(
|
|
143
|
+
self,
|
|
144
|
+
*,
|
|
145
|
+
profile: str,
|
|
146
|
+
app_key: str | None,
|
|
147
|
+
) -> dict[str, Any]:
|
|
148
|
+
raw = self.task_statistics(profile=profile, app_key=app_key)
|
|
149
|
+
statistics = raw.get("statistics", {})
|
|
150
|
+
summary = self._normalize_task_summary_payload(statistics)
|
|
151
|
+
return {
|
|
152
|
+
"profile": profile,
|
|
153
|
+
"ws_id": raw.get("ws_id"),
|
|
154
|
+
"ok": True,
|
|
155
|
+
"request_route": raw.get("request_route"),
|
|
156
|
+
"warnings": [],
|
|
157
|
+
"output_profile": "normal",
|
|
158
|
+
"data": {
|
|
159
|
+
"summary": summary,
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
def task_list_public(
|
|
164
|
+
self,
|
|
165
|
+
*,
|
|
166
|
+
profile: str,
|
|
167
|
+
task_box: str,
|
|
168
|
+
flow_status: str,
|
|
169
|
+
app_key: str | None,
|
|
170
|
+
workflow_node_id: int | None,
|
|
171
|
+
query: str | None,
|
|
172
|
+
page: int,
|
|
173
|
+
page_size: int,
|
|
174
|
+
sort_by: str | None,
|
|
175
|
+
sort_direction: str,
|
|
176
|
+
) -> dict[str, Any]:
|
|
177
|
+
normalized_type = self._task_box_to_type(task_box)
|
|
178
|
+
normalized_status = self._flow_status_to_process_status(flow_status)
|
|
179
|
+
create_time_asc = self._task_sort_to_create_time_asc(sort_by, sort_direction)
|
|
180
|
+
raw = self.task_list(
|
|
181
|
+
profile=profile,
|
|
182
|
+
type=normalized_type,
|
|
183
|
+
process_status=normalized_status,
|
|
184
|
+
app_key=app_key,
|
|
185
|
+
node_id=workflow_node_id,
|
|
186
|
+
search_key=query,
|
|
187
|
+
page_num=page,
|
|
188
|
+
page_size=page_size,
|
|
189
|
+
create_time_asc=create_time_asc,
|
|
190
|
+
)
|
|
191
|
+
task_page = raw.get("page", {})
|
|
192
|
+
return {
|
|
193
|
+
"profile": profile,
|
|
194
|
+
"ws_id": raw.get("ws_id"),
|
|
195
|
+
"ok": True,
|
|
196
|
+
"request_route": raw.get("request_route"),
|
|
197
|
+
"warnings": [],
|
|
198
|
+
"output_profile": "normal",
|
|
199
|
+
"data": {
|
|
200
|
+
"items": _task_page_items(task_page),
|
|
201
|
+
"pagination": {
|
|
202
|
+
"page": page,
|
|
203
|
+
"page_size": page_size,
|
|
204
|
+
"returned_items": len(_task_page_items(task_page)),
|
|
205
|
+
"page_amount": _task_page_amount(task_page),
|
|
206
|
+
"reported_total": _task_page_total(task_page),
|
|
207
|
+
},
|
|
208
|
+
"selection": {
|
|
209
|
+
"task_box": task_box,
|
|
210
|
+
"flow_status": flow_status,
|
|
211
|
+
"app_key": app_key,
|
|
212
|
+
"workflow_node_id": workflow_node_id,
|
|
213
|
+
"query": query,
|
|
214
|
+
"applied_sort": self._task_applied_sort(sort_by, sort_direction),
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
def task_facets(
|
|
220
|
+
self,
|
|
221
|
+
*,
|
|
222
|
+
profile: str,
|
|
223
|
+
task_box: str,
|
|
224
|
+
flow_status: str,
|
|
225
|
+
dimension: str,
|
|
226
|
+
app_key: str | None,
|
|
227
|
+
query: str | None,
|
|
228
|
+
limit: int,
|
|
229
|
+
) -> dict[str, Any]:
|
|
230
|
+
normalized_type = self._task_box_to_type(task_box)
|
|
231
|
+
normalized_status = self._flow_status_to_process_status(flow_status)
|
|
232
|
+
if dimension not in {"worksheet", "workflow_node"}:
|
|
233
|
+
raise_tool_error(QingflowApiError.config_error("dimension must be worksheet or workflow_node"))
|
|
234
|
+
if limit <= 0:
|
|
235
|
+
raise_tool_error(QingflowApiError.config_error("limit must be positive"))
|
|
236
|
+
|
|
237
|
+
if dimension == "worksheet":
|
|
238
|
+
raw = self.task_worksheet_statistics(
|
|
239
|
+
profile=profile,
|
|
240
|
+
type=normalized_type,
|
|
241
|
+
worksheet_name=query,
|
|
242
|
+
page_num=1,
|
|
243
|
+
page_size=max(limit, 20),
|
|
244
|
+
)
|
|
245
|
+
source = raw.get("page", {})
|
|
246
|
+
elif app_key:
|
|
247
|
+
raw = self.task_node_statistics(
|
|
308
248
|
profile=profile,
|
|
309
249
|
app_key=app_key,
|
|
310
|
-
|
|
311
|
-
|
|
250
|
+
type=normalized_type,
|
|
251
|
+
search_key=query,
|
|
312
252
|
)
|
|
253
|
+
source = raw.get("nodes", {})
|
|
254
|
+
else:
|
|
255
|
+
raw = self.task_workflow_nodes(
|
|
256
|
+
profile=profile,
|
|
257
|
+
type=normalized_type,
|
|
258
|
+
status=str(normalized_status),
|
|
259
|
+
app_key_list=None,
|
|
260
|
+
search_key=query,
|
|
261
|
+
page_num=1,
|
|
262
|
+
page_size=max(limit, 20),
|
|
263
|
+
)
|
|
264
|
+
source = raw.get("page", {})
|
|
265
|
+
|
|
266
|
+
groups = self._normalize_task_facets(source)
|
|
267
|
+
rows_truncated = len(groups) > limit
|
|
268
|
+
returned_groups = groups[:limit]
|
|
269
|
+
return {
|
|
270
|
+
"profile": profile,
|
|
271
|
+
"ws_id": raw.get("ws_id"),
|
|
272
|
+
"ok": True,
|
|
273
|
+
"request_route": raw.get("request_route"),
|
|
274
|
+
"warnings": [],
|
|
275
|
+
"output_profile": "normal",
|
|
276
|
+
"data": {
|
|
277
|
+
"groups": returned_groups,
|
|
278
|
+
"rows_truncated": rows_truncated,
|
|
279
|
+
"statement_scope": "returned_groups_only" if rows_truncated else "full_population",
|
|
280
|
+
"selection": {
|
|
281
|
+
"task_box": task_box,
|
|
282
|
+
"flow_status": flow_status,
|
|
283
|
+
"dimension": dimension,
|
|
284
|
+
"app_key": app_key,
|
|
285
|
+
"query": query,
|
|
286
|
+
"limit": limit,
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
def task_mark_read_public(
|
|
292
|
+
self,
|
|
293
|
+
*,
|
|
294
|
+
profile: str,
|
|
295
|
+
app_key: str,
|
|
296
|
+
task_id: int,
|
|
297
|
+
task_box: str,
|
|
298
|
+
) -> dict[str, Any]:
|
|
299
|
+
raw = self.task_mark_read(profile=profile, app_key=app_key, id=task_id, type=self._task_box_to_type(task_box))
|
|
300
|
+
return self._public_task_action_response(
|
|
301
|
+
raw,
|
|
302
|
+
action="mark_read",
|
|
303
|
+
resource={"app_key": app_key, "task_id": task_id},
|
|
304
|
+
selection={"task_box": task_box},
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
def task_mark_all_cc_read_public(
|
|
308
|
+
self,
|
|
309
|
+
*,
|
|
310
|
+
profile: str,
|
|
311
|
+
flow_status: str,
|
|
312
|
+
) -> dict[str, Any]:
|
|
313
|
+
raw = self.task_mark_all_cc_read(profile=profile, type=TASK_BOX_TO_TYPE["cc"], process_status=self._flow_status_to_process_status(flow_status))
|
|
314
|
+
return self._public_task_action_response(
|
|
315
|
+
raw,
|
|
316
|
+
action="mark_all_cc_read",
|
|
317
|
+
resource={},
|
|
318
|
+
selection={"task_box": "cc", "flow_status": flow_status},
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
def task_urge_public(
|
|
322
|
+
self,
|
|
323
|
+
*,
|
|
324
|
+
profile: str,
|
|
325
|
+
app_key: str,
|
|
326
|
+
record_id: int,
|
|
327
|
+
) -> dict[str, Any]:
|
|
328
|
+
raw = self.task_urge(profile=profile, app_key=app_key, row_record_id=record_id)
|
|
329
|
+
return self._public_task_action_response(
|
|
330
|
+
raw,
|
|
331
|
+
action="urge",
|
|
332
|
+
resource={"app_key": app_key, "record_id": record_id},
|
|
333
|
+
selection={},
|
|
334
|
+
)
|
|
313
335
|
|
|
314
336
|
def task_list(
|
|
315
337
|
self,
|
|
@@ -347,6 +369,7 @@ class TaskTools(ToolBase):
|
|
|
347
369
|
return {
|
|
348
370
|
"profile": profile,
|
|
349
371
|
"ws_id": session_profile.selected_ws_id,
|
|
372
|
+
"request_route": self._request_route_payload(context),
|
|
350
373
|
"type": type,
|
|
351
374
|
"type_label": get_task_type_label(type),
|
|
352
375
|
"list_type_label": get_task_type_label(type),
|
|
@@ -389,6 +412,7 @@ class TaskTools(ToolBase):
|
|
|
389
412
|
return {
|
|
390
413
|
"profile": profile,
|
|
391
414
|
"ws_id": session_profile.selected_ws_id,
|
|
415
|
+
"request_route": self._request_route_payload(context),
|
|
392
416
|
"type": type,
|
|
393
417
|
"type_label": get_task_type_label(type),
|
|
394
418
|
"list_type_label": get_task_type_label(type),
|
|
@@ -413,6 +437,7 @@ class TaskTools(ToolBase):
|
|
|
413
437
|
return {
|
|
414
438
|
"profile": profile,
|
|
415
439
|
"ws_id": session_profile.selected_ws_id,
|
|
440
|
+
"request_route": self._request_route_payload(context),
|
|
416
441
|
"statistics": result,
|
|
417
442
|
}
|
|
418
443
|
|
|
@@ -448,6 +473,7 @@ class TaskTools(ToolBase):
|
|
|
448
473
|
return {
|
|
449
474
|
"profile": profile,
|
|
450
475
|
"ws_id": session_profile.selected_ws_id,
|
|
476
|
+
"request_route": self._request_route_payload(context),
|
|
451
477
|
"type": type,
|
|
452
478
|
"type_label": get_task_type_label(type),
|
|
453
479
|
"list_type_label": get_task_type_label(type),
|
|
@@ -480,6 +506,7 @@ class TaskTools(ToolBase):
|
|
|
480
506
|
return {
|
|
481
507
|
"profile": profile,
|
|
482
508
|
"ws_id": session_profile.selected_ws_id,
|
|
509
|
+
"request_route": self._request_route_payload(context),
|
|
483
510
|
"app_key": app_key,
|
|
484
511
|
"type": type,
|
|
485
512
|
"type_label": get_task_type_label(type),
|
|
@@ -513,6 +540,7 @@ class TaskTools(ToolBase):
|
|
|
513
540
|
return {
|
|
514
541
|
"profile": profile,
|
|
515
542
|
"ws_id": session_profile.selected_ws_id,
|
|
543
|
+
"request_route": self._request_route_payload(context),
|
|
516
544
|
"type": type,
|
|
517
545
|
"type_label": get_task_type_label(type),
|
|
518
546
|
"list_type_label": get_task_type_label(type),
|
|
@@ -544,6 +572,7 @@ class TaskTools(ToolBase):
|
|
|
544
572
|
return {
|
|
545
573
|
"profile": profile,
|
|
546
574
|
"ws_id": session_profile.selected_ws_id,
|
|
575
|
+
"request_route": self._request_route_payload(context),
|
|
547
576
|
"app_key": app_key,
|
|
548
577
|
"id": id,
|
|
549
578
|
"type": type,
|
|
@@ -573,6 +602,7 @@ class TaskTools(ToolBase):
|
|
|
573
602
|
return {
|
|
574
603
|
"profile": profile,
|
|
575
604
|
"ws_id": session_profile.selected_ws_id,
|
|
605
|
+
"request_route": self._request_route_payload(context),
|
|
576
606
|
"type": type,
|
|
577
607
|
"type_label": get_task_type_label(type),
|
|
578
608
|
"list_type_label": get_task_type_label(type),
|
|
@@ -603,6 +633,7 @@ class TaskTools(ToolBase):
|
|
|
603
633
|
return {
|
|
604
634
|
"profile": profile,
|
|
605
635
|
"ws_id": session_profile.selected_ws_id,
|
|
636
|
+
"request_route": self._request_route_payload(context),
|
|
606
637
|
"app_key": app_key,
|
|
607
638
|
"row_record_id": row_record_id,
|
|
608
639
|
"result": result,
|
|
@@ -631,6 +662,7 @@ class TaskTools(ToolBase):
|
|
|
631
662
|
return {
|
|
632
663
|
"profile": profile,
|
|
633
664
|
"ws_id": session_profile.selected_ws_id,
|
|
665
|
+
"request_route": self._request_route_payload(context),
|
|
634
666
|
"app_key": app_key,
|
|
635
667
|
"group_id": group_id,
|
|
636
668
|
"detail": result,
|
|
@@ -665,6 +697,7 @@ class TaskTools(ToolBase):
|
|
|
665
697
|
return {
|
|
666
698
|
"profile": profile,
|
|
667
699
|
"ws_id": session_profile.selected_ws_id,
|
|
700
|
+
"request_route": self._request_route_payload(context),
|
|
668
701
|
"app_key": app_key,
|
|
669
702
|
"list_type": list_type,
|
|
670
703
|
"list_type_label": get_record_list_type_label(list_type),
|
|
@@ -690,3 +723,121 @@ class TaskTools(ToolBase):
|
|
|
690
723
|
f"Invalid process_status: {process_status}. Must be one of {valid_statuses}"
|
|
691
724
|
)
|
|
692
725
|
)
|
|
726
|
+
|
|
727
|
+
def _task_box_to_type(self, task_box: str) -> int:
|
|
728
|
+
normalized = (task_box or "").strip().lower()
|
|
729
|
+
if normalized not in TASK_BOX_TO_TYPE:
|
|
730
|
+
raise_tool_error(QingflowApiError.config_error("task_box must be todo, initiated, cc, or done"))
|
|
731
|
+
return TASK_BOX_TO_TYPE[normalized]
|
|
732
|
+
|
|
733
|
+
def _flow_status_to_process_status(self, flow_status: str) -> int:
|
|
734
|
+
normalized = (flow_status or "").strip().lower()
|
|
735
|
+
if normalized not in FLOW_STATUS_TO_PROCESS_STATUS:
|
|
736
|
+
raise_tool_error(
|
|
737
|
+
QingflowApiError.config_error(
|
|
738
|
+
"flow_status must be all, in_progress, approved, rejected, pending_fix, urged, overdue, due_soon, unread, or ended"
|
|
739
|
+
)
|
|
740
|
+
)
|
|
741
|
+
return FLOW_STATUS_TO_PROCESS_STATUS[normalized]
|
|
742
|
+
|
|
743
|
+
def _task_sort_to_create_time_asc(self, sort_by: str | None, sort_direction: str) -> bool | None:
|
|
744
|
+
normalized_sort_by = (sort_by or "").strip().lower() if sort_by is not None else ""
|
|
745
|
+
normalized_direction = (sort_direction or "desc").strip().lower()
|
|
746
|
+
if normalized_direction not in {"asc", "desc"}:
|
|
747
|
+
raise_tool_error(QingflowApiError.config_error("sort_direction must be asc or desc"))
|
|
748
|
+
if not normalized_sort_by:
|
|
749
|
+
return None
|
|
750
|
+
if normalized_sort_by not in {"created_at", "create_time"}:
|
|
751
|
+
raise_tool_error(QingflowApiError.config_error("task_list only supports sort_by=created_at"))
|
|
752
|
+
return normalized_direction == "asc"
|
|
753
|
+
|
|
754
|
+
def _task_applied_sort(self, sort_by: str | None, sort_direction: str) -> list[dict[str, Any]]:
|
|
755
|
+
if not sort_by:
|
|
756
|
+
return []
|
|
757
|
+
return [{"by": "created_at", "order": (sort_direction or "desc").strip().lower()}]
|
|
758
|
+
|
|
759
|
+
def _normalize_task_summary_payload(self, payload: Any) -> dict[str, Any]:
|
|
760
|
+
if not isinstance(payload, dict):
|
|
761
|
+
return {}
|
|
762
|
+
return {
|
|
763
|
+
"todo_count": payload.get("todoCount", payload.get("todo_count")),
|
|
764
|
+
"overdue_count": payload.get("timeoutCount", payload.get("timeout_count")),
|
|
765
|
+
"due_soon_count": payload.get("preTimeoutCount", payload.get("pre_timeout_count")),
|
|
766
|
+
"urged_count": payload.get("urgedCount", payload.get("urged_count")),
|
|
767
|
+
"cc_unread_count": payload.get("ccUnreadCount", payload.get("cc_unread_count")),
|
|
768
|
+
"in_progress_initiated_count": payload.get("processingCount", payload.get("processing_count")),
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
def _normalize_task_facets(self, payload: Any) -> list[dict[str, Any]]:
|
|
772
|
+
items = _task_page_items(payload)
|
|
773
|
+
groups: list[dict[str, Any]] = []
|
|
774
|
+
for item in items:
|
|
775
|
+
if not isinstance(item, dict):
|
|
776
|
+
continue
|
|
777
|
+
key = item.get("worksheetId", item.get("groupId", item.get("nodeId", item.get("id", item.get("key")))))
|
|
778
|
+
label = item.get("worksheetName", item.get("groupName", item.get("nodeName", item.get("name", item.get("label", item.get("title"))))))
|
|
779
|
+
count = item.get("count", item.get("taskCount", item.get("amount", item.get("todoCount", item.get("num")))))
|
|
780
|
+
groups.append({"key": key if key is not None else label, "label": label, "count": count})
|
|
781
|
+
return groups
|
|
782
|
+
|
|
783
|
+
def _public_task_action_response(
|
|
784
|
+
self,
|
|
785
|
+
raw: dict[str, Any],
|
|
786
|
+
*,
|
|
787
|
+
action: str,
|
|
788
|
+
resource: dict[str, Any],
|
|
789
|
+
selection: dict[str, Any],
|
|
790
|
+
) -> dict[str, Any]:
|
|
791
|
+
response = dict(raw)
|
|
792
|
+
response["ok"] = bool(raw.get("ok", True))
|
|
793
|
+
response["warnings"] = []
|
|
794
|
+
response["output_profile"] = "normal"
|
|
795
|
+
response["data"] = {
|
|
796
|
+
"action": action,
|
|
797
|
+
"resource": resource,
|
|
798
|
+
"selection": selection,
|
|
799
|
+
"result": raw.get("result"),
|
|
800
|
+
}
|
|
801
|
+
return response
|
|
802
|
+
|
|
803
|
+
def _request_route_payload(self, context) -> dict[str, Any]: # type: ignore[no-untyped-def]
|
|
804
|
+
describe_route = getattr(self.backend, "describe_route", None)
|
|
805
|
+
if callable(describe_route):
|
|
806
|
+
payload = describe_route(context)
|
|
807
|
+
if isinstance(payload, dict):
|
|
808
|
+
return payload
|
|
809
|
+
return {
|
|
810
|
+
"base_url": getattr(context, "base_url", None),
|
|
811
|
+
"qf_version": getattr(context, "qf_version", None),
|
|
812
|
+
"qf_version_source": getattr(context, "qf_version_source", None) or ("context" if getattr(context, "qf_version", None) else "unknown"),
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
def _task_page_items(payload: Any) -> list[Any]:
|
|
817
|
+
if isinstance(payload, list):
|
|
818
|
+
return payload
|
|
819
|
+
if not isinstance(payload, dict):
|
|
820
|
+
return []
|
|
821
|
+
for key in ("list", "items", "rows", "result"):
|
|
822
|
+
value = payload.get(key)
|
|
823
|
+
if isinstance(value, list):
|
|
824
|
+
return value
|
|
825
|
+
for container_key in ("page", "data"):
|
|
826
|
+
nested = payload.get(container_key)
|
|
827
|
+
if isinstance(nested, dict):
|
|
828
|
+
nested_items = _task_page_items(nested)
|
|
829
|
+
if nested_items:
|
|
830
|
+
return nested_items
|
|
831
|
+
return []
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
def _task_page_amount(payload: Any) -> Any:
|
|
835
|
+
if isinstance(payload, dict):
|
|
836
|
+
return payload.get("pageAmount", payload.get("page_amount"))
|
|
837
|
+
return None
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
def _task_page_total(payload: Any) -> Any:
|
|
841
|
+
if isinstance(payload, dict):
|
|
842
|
+
return payload.get("total", payload.get("count"))
|
|
843
|
+
return None
|