@josephyan/qingflow-cli 0.2.0-beta.61 → 0.2.0-beta.63

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.
@@ -69,12 +69,24 @@ class ApprovalTools(ToolBase):
69
69
  return self.record_comment_mark_read(profile=profile, app_key=app_key, apply_id=record_id)
70
70
 
71
71
  @mcp.tool(description=self._high_risk_tool_description(operation="approve", target="workflow task"))
72
- def task_approve(profile: str = DEFAULT_PROFILE, app_key: str = "", record_id: int = 0, payload: dict[str, Any] | None = None) -> dict[str, Any]:
73
- return self.task_approve(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {})
72
+ def task_approve(
73
+ profile: str = DEFAULT_PROFILE,
74
+ app_key: str = "",
75
+ record_id: int = 0,
76
+ payload: dict[str, Any] | None = None,
77
+ fields: dict[str, Any] | None = None,
78
+ ) -> dict[str, Any]:
79
+ return self.task_approve(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {}, fields=fields or {})
74
80
 
75
81
  @mcp.tool(description=self._high_risk_tool_description(operation="reject", target="workflow task"))
76
- def task_reject(profile: str = DEFAULT_PROFILE, app_key: str = "", record_id: int = 0, payload: dict[str, Any] | None = None) -> dict[str, Any]:
77
- return self.task_reject(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {})
82
+ def task_reject(
83
+ profile: str = DEFAULT_PROFILE,
84
+ app_key: str = "",
85
+ record_id: int = 0,
86
+ payload: dict[str, Any] | None = None,
87
+ fields: dict[str, Any] | None = None,
88
+ ) -> dict[str, Any]:
89
+ return self.task_reject(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {}, fields=fields or {})
78
90
 
79
91
  @mcp.tool()
80
92
  def task_rollback_candidates(
@@ -86,12 +98,40 @@ class ApprovalTools(ToolBase):
86
98
  return self.task_rollback_candidates(profile=profile, app_key=app_key, record_id=record_id, workflow_node_id=workflow_node_id)
87
99
 
88
100
  @mcp.tool()
89
- def task_rollback(profile: str = DEFAULT_PROFILE, app_key: str = "", record_id: int = 0, payload: dict[str, Any] | None = None) -> dict[str, Any]:
90
- return self.task_rollback(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {})
101
+ def task_rollback(
102
+ profile: str = DEFAULT_PROFILE,
103
+ app_key: str = "",
104
+ record_id: int = 0,
105
+ payload: dict[str, Any] | None = None,
106
+ fields: dict[str, Any] | None = None,
107
+ ) -> dict[str, Any]:
108
+ return self.task_rollback(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {}, fields=fields or {})
91
109
 
92
110
  @mcp.tool()
93
- def task_transfer(profile: str = DEFAULT_PROFILE, app_key: str = "", record_id: int = 0, payload: dict[str, Any] | None = None) -> dict[str, Any]:
94
- return self.task_transfer(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {})
111
+ def task_transfer(
112
+ profile: str = DEFAULT_PROFILE,
113
+ app_key: str = "",
114
+ record_id: int = 0,
115
+ payload: dict[str, Any] | None = None,
116
+ fields: dict[str, Any] | None = None,
117
+ ) -> dict[str, Any]:
118
+ return self.task_transfer(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {}, fields=fields or {})
119
+
120
+ @mcp.tool(description=self._high_risk_tool_description(operation="save", target="workflow task fields without advancing the workflow"))
121
+ def task_save_only(
122
+ profile: str = DEFAULT_PROFILE,
123
+ app_key: str = "",
124
+ record_id: int = 0,
125
+ workflow_node_id: int = 0,
126
+ fields: dict[str, Any] | None = None,
127
+ ) -> dict[str, Any]:
128
+ return self.task_save_only(
129
+ profile=profile,
130
+ app_key=app_key,
131
+ record_id=record_id,
132
+ workflow_node_id=workflow_node_id,
133
+ fields=fields or {},
134
+ )
95
135
 
96
136
  @mcp.tool()
97
137
  def task_transfer_candidates(
@@ -156,7 +196,17 @@ class ApprovalTools(ToolBase):
156
196
  selection={"app_key": app_key, "record_id": record_id, "list_type": list_type, "keyword": keyword},
157
197
  )
158
198
 
159
- def task_approve(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any]) -> dict[str, Any]:
199
+ def task_approve(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any], fields: dict[str, Any] | None = None) -> dict[str, Any]:
200
+ if fields:
201
+ return self._delegate_task_action(
202
+ profile=profile,
203
+ app_key=app_key,
204
+ record_id=record_id,
205
+ action="approve",
206
+ payload=payload,
207
+ fields=fields,
208
+ public_action="task_approve",
209
+ )
160
210
  raw = self.record_approve(profile=profile, app_key=app_key, apply_id=record_id, payload=payload)
161
211
  return self._public_action_response(
162
212
  raw,
@@ -166,7 +216,17 @@ class ApprovalTools(ToolBase):
166
216
  human_review=True,
167
217
  )
168
218
 
169
- def task_reject(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any]) -> dict[str, Any]:
219
+ def task_reject(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any], fields: dict[str, Any] | None = None) -> dict[str, Any]:
220
+ if fields:
221
+ return self._delegate_task_action(
222
+ profile=profile,
223
+ app_key=app_key,
224
+ record_id=record_id,
225
+ action="reject",
226
+ payload=payload,
227
+ fields=fields,
228
+ public_action="task_reject",
229
+ )
170
230
  raw = self.record_reject(profile=profile, app_key=app_key, apply_id=record_id, payload=payload)
171
231
  return self._public_action_response(
172
232
  raw,
@@ -186,7 +246,17 @@ class ApprovalTools(ToolBase):
186
246
  selection={"app_key": app_key, "record_id": record_id, "workflow_node_id": workflow_node_id},
187
247
  )
188
248
 
189
- def task_rollback(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any]) -> dict[str, Any]:
249
+ def task_rollback(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any], fields: dict[str, Any] | None = None) -> dict[str, Any]:
250
+ if fields:
251
+ return self._delegate_task_action(
252
+ profile=profile,
253
+ app_key=app_key,
254
+ record_id=record_id,
255
+ action="rollback",
256
+ payload=payload,
257
+ fields=fields,
258
+ public_action="task_rollback",
259
+ )
190
260
  raw = self.record_rollback(profile=profile, app_key=app_key, apply_id=record_id, payload=payload)
191
261
  return self._public_action_response(
192
262
  raw,
@@ -196,7 +266,17 @@ class ApprovalTools(ToolBase):
196
266
  human_review=True,
197
267
  )
198
268
 
199
- def task_transfer(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any]) -> dict[str, Any]:
269
+ def task_transfer(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any], fields: dict[str, Any] | None = None) -> dict[str, Any]:
270
+ if fields:
271
+ return self._delegate_task_action(
272
+ profile=profile,
273
+ app_key=app_key,
274
+ record_id=record_id,
275
+ action="transfer",
276
+ payload=payload,
277
+ fields=fields,
278
+ public_action="task_transfer",
279
+ )
200
280
  raw = self.record_transfer(profile=profile, app_key=app_key, apply_id=record_id, payload=payload)
201
281
  return self._public_action_response(
202
282
  raw,
@@ -206,6 +286,26 @@ class ApprovalTools(ToolBase):
206
286
  human_review=True,
207
287
  )
208
288
 
289
+ def task_save_only(
290
+ self,
291
+ *,
292
+ profile: str,
293
+ app_key: str,
294
+ record_id: int,
295
+ workflow_node_id: int,
296
+ fields: dict[str, Any],
297
+ ) -> dict[str, Any]:
298
+ return self._delegate_task_action(
299
+ profile=profile,
300
+ app_key=app_key,
301
+ record_id=record_id,
302
+ action="save_only",
303
+ payload={},
304
+ fields=fields,
305
+ public_action="task_save_only",
306
+ workflow_node_id=workflow_node_id,
307
+ )
308
+
209
309
  def task_transfer_candidates(
210
310
  self,
211
311
  *,
@@ -749,6 +849,41 @@ class ApprovalTools(ToolBase):
749
849
  if payload.get("handSignImageUrl"):
750
850
  raise_tool_error(QingflowApiError.not_supported("NOT_SUPPORTED_IN_V1: handSignImageUrl is not supported"))
751
851
 
852
+ def _delegate_task_action(
853
+ self,
854
+ *,
855
+ profile: str,
856
+ app_key: str,
857
+ record_id: int,
858
+ action: str,
859
+ payload: dict[str, Any],
860
+ fields: dict[str, Any],
861
+ public_action: str,
862
+ workflow_node_id: int | None = None,
863
+ ) -> dict[str, Any]:
864
+ from .task_context_tools import TaskContextTools
865
+
866
+ node_id = workflow_node_id
867
+ if node_id is None:
868
+ node_payload = dict(payload or {})
869
+ node_id = self._extract_node_id(node_payload)
870
+ delegated = TaskContextTools(self.sessions, self.backend).task_action_execute(
871
+ profile=profile,
872
+ app_key=app_key,
873
+ record_id=record_id,
874
+ workflow_node_id=node_id,
875
+ action=action,
876
+ payload=payload or {},
877
+ fields=fields or {},
878
+ )
879
+ response = dict(delegated)
880
+ data = response.get("data")
881
+ if isinstance(data, dict):
882
+ payload_data = dict(data)
883
+ payload_data["action"] = public_action
884
+ response["data"] = payload_data
885
+ return response
886
+
752
887
  def _public_page_response(
753
888
  self,
754
889
  raw: dict[str, Any],
@@ -0,0 +1,177 @@
1
+ from __future__ import annotations
2
+
3
+ from copy import deepcopy
4
+
5
+ from ..config import DEFAULT_PROFILE
6
+ from ..errors import QingflowApiError, raise_tool_error
7
+ from ..json_types import JSONObject
8
+ from .base import ToolBase
9
+
10
+
11
+ class CustomButtonTools(ToolBase):
12
+ def custom_button_list(
13
+ self,
14
+ *,
15
+ profile: str,
16
+ app_key: str,
17
+ being_draft: bool = True,
18
+ include_raw: bool = False,
19
+ ) -> JSONObject:
20
+ self._require_app_key(app_key)
21
+
22
+ def runner(session_profile, context):
23
+ result = self.backend.request(
24
+ "GET",
25
+ context,
26
+ f"/app/{app_key}/customButton",
27
+ params={"beingDraft": being_draft},
28
+ )
29
+ items = []
30
+ raw_items = result.get("result") if isinstance(result, dict) and isinstance(result.get("result"), list) else []
31
+ for item in raw_items:
32
+ if not isinstance(item, dict):
33
+ continue
34
+ items.append(self._compact_button_base_info(item))
35
+ response = {
36
+ "profile": profile,
37
+ "ws_id": session_profile.selected_ws_id,
38
+ "app_key": app_key,
39
+ "being_draft": being_draft,
40
+ "items": result if include_raw else items,
41
+ "count": len(items),
42
+ "compact": not include_raw,
43
+ }
44
+ if include_raw:
45
+ response["summary"] = items
46
+ return response
47
+
48
+ return self._run(profile, runner)
49
+
50
+ def custom_button_get(
51
+ self,
52
+ *,
53
+ profile: str,
54
+ app_key: str,
55
+ button_id: int,
56
+ being_draft: bool = True,
57
+ include_raw: bool = False,
58
+ ) -> JSONObject:
59
+ self._require_app_key(app_key)
60
+ self._require_button_id(button_id)
61
+
62
+ def runner(session_profile, context):
63
+ params = {"beingDraft": being_draft}
64
+ result = self.backend.request("GET", context, f"/app/{app_key}/customButton/{button_id}", params=params)
65
+ response = {
66
+ "profile": profile,
67
+ "ws_id": session_profile.selected_ws_id,
68
+ "app_key": app_key,
69
+ "button_id": button_id,
70
+ "being_draft": being_draft,
71
+ "result": result if include_raw else self._compact_button_detail(result if isinstance(result, dict) else {}),
72
+ "compact": not include_raw,
73
+ }
74
+ if include_raw:
75
+ response["summary"] = self._compact_button_detail(result if isinstance(result, dict) else {})
76
+ return response
77
+
78
+ return self._run(profile, runner)
79
+
80
+ def custom_button_create(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
81
+ self._require_app_key(app_key)
82
+ body = self._require_dict(payload)
83
+
84
+ def runner(session_profile, context):
85
+ result = self.backend.request("POST", context, f"/app/{app_key}/customButton", json_body=deepcopy(body))
86
+ return {"profile": profile, "ws_id": session_profile.selected_ws_id, "app_key": app_key, "result": result}
87
+
88
+ return self._run(profile, runner)
89
+
90
+ def custom_button_update(self, *, profile: str, app_key: str, button_id: int, payload: JSONObject) -> JSONObject:
91
+ self._require_app_key(app_key)
92
+ self._require_button_id(button_id)
93
+ body = self._require_dict(payload)
94
+
95
+ def runner(session_profile, context):
96
+ result = self.backend.request(
97
+ "POST",
98
+ context,
99
+ f"/app/{app_key}/customButton/{button_id}",
100
+ json_body=deepcopy(body),
101
+ )
102
+ return self._attach_human_review_notice(
103
+ {
104
+ "profile": profile,
105
+ "ws_id": session_profile.selected_ws_id,
106
+ "app_key": app_key,
107
+ "button_id": button_id,
108
+ "result": result,
109
+ },
110
+ operation="update",
111
+ target="custom button configuration",
112
+ )
113
+
114
+ return self._run(profile, runner)
115
+
116
+ def custom_button_delete(self, *, profile: str, app_key: str, button_id: int) -> JSONObject:
117
+ self._require_app_key(app_key)
118
+ self._require_button_id(button_id)
119
+
120
+ def runner(session_profile, context):
121
+ result = self.backend.request("DELETE", context, f"/app/{app_key}/customButton/{button_id}")
122
+ return self._attach_human_review_notice(
123
+ {
124
+ "profile": profile,
125
+ "ws_id": session_profile.selected_ws_id,
126
+ "app_key": app_key,
127
+ "button_id": button_id,
128
+ "result": result,
129
+ },
130
+ operation="delete",
131
+ target="custom button configuration",
132
+ )
133
+
134
+ return self._run(profile, runner)
135
+
136
+ def _require_app_key(self, app_key: str) -> None:
137
+ if not str(app_key or "").strip():
138
+ raise_tool_error(QingflowApiError.config_error("app_key is required"))
139
+
140
+ def _require_button_id(self, button_id: int) -> None:
141
+ if not isinstance(button_id, int) or isinstance(button_id, bool) or button_id <= 0:
142
+ raise_tool_error(QingflowApiError.config_error("button_id must be a positive integer"))
143
+
144
+ def _compact_button_base_info(self, item: dict[str, object]) -> JSONObject:
145
+ creator = item.get("creatorUserInfo") if isinstance(item.get("creatorUserInfo"), dict) else {}
146
+ return {
147
+ "button_id": item.get("buttonId"),
148
+ "button_text": item.get("buttonText"),
149
+ "button_icon": item.get("buttonIcon"),
150
+ "background_color": item.get("backgroundColor"),
151
+ "text_color": item.get("textColor"),
152
+ "creator_user_info": {
153
+ "uid": creator.get("uid"),
154
+ "name": creator.get("name"),
155
+ "email": creator.get("email"),
156
+ }
157
+ if creator
158
+ else None,
159
+ "used_in_chart_count": item.get("userInChartCount"),
160
+ "being_effective_external_qrobot": item.get("beingEffectiveExternalQRobot"),
161
+ }
162
+
163
+ def _compact_button_detail(self, item: dict[str, object]) -> JSONObject:
164
+ return {
165
+ "button_id": item.get("buttonId"),
166
+ "button_text": item.get("buttonText"),
167
+ "button_icon": item.get("buttonIcon"),
168
+ "background_color": item.get("backgroundColor"),
169
+ "text_color": item.get("textColor"),
170
+ "trigger_action": item.get("triggerAction"),
171
+ "trigger_link_url": item.get("triggerLinkUrl"),
172
+ "trigger_add_data_config": deepcopy(item.get("triggerAddDataConfig")) if isinstance(item.get("triggerAddDataConfig"), dict) else None,
173
+ "external_qrobot_config": deepcopy(item.get("customButtonExternalQRobotRelationVO"))
174
+ if isinstance(item.get("customButtonExternalQRobotRelationVO"), dict)
175
+ else None,
176
+ "trigger_wings_config": deepcopy(item.get("triggerWingsConfig")) if isinstance(item.get("triggerWingsConfig"), dict) else None,
177
+ }