@josephyan/qingflow-cli 0.2.0-beta.985 → 0.2.0-beta.987
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/docs/local-agent-install.md +70 -11
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/builder_facade/service.py +376 -19
- package/src/qingflow_mcp/cli/commands/auth.py +14 -43
- package/src/qingflow_mcp/cli/commands/workspace.py +8 -5
- package/src/qingflow_mcp/cli/formatters.py +19 -22
- package/src/qingflow_mcp/config.py +39 -0
- package/src/qingflow_mcp/errors.py +2 -2
- package/src/qingflow_mcp/public_surface.py +4 -6
- package/src/qingflow_mcp/response_trim.py +1 -8
- package/src/qingflow_mcp/server.py +1 -1
- package/src/qingflow_mcp/server_app_builder.py +4 -28
- package/src/qingflow_mcp/server_app_user.py +4 -28
- package/src/qingflow_mcp/session_store.py +31 -5
- package/src/qingflow_mcp/solution/compiler/form_compiler.py +2 -2
- package/src/qingflow_mcp/solution/executor.py +2 -2
- package/src/qingflow_mcp/tools/ai_builder_tools.py +117 -1
- package/src/qingflow_mcp/tools/app_tools.py +51 -1
- package/src/qingflow_mcp/tools/approval_tools.py +82 -1
- package/src/qingflow_mcp/tools/auth_tools.py +306 -288
- package/src/qingflow_mcp/tools/base.py +204 -4
- package/src/qingflow_mcp/tools/code_block_tools.py +21 -0
- package/src/qingflow_mcp/tools/custom_button_tools.py +24 -1
- package/src/qingflow_mcp/tools/directory_tools.py +28 -1
- package/src/qingflow_mcp/tools/feedback_tools.py +8 -0
- package/src/qingflow_mcp/tools/file_tools.py +25 -1
- package/src/qingflow_mcp/tools/import_tools.py +40 -1
- package/src/qingflow_mcp/tools/navigation_tools.py +34 -1
- package/src/qingflow_mcp/tools/package_tools.py +37 -1
- package/src/qingflow_mcp/tools/portal_tools.py +28 -1
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +38 -1
- package/src/qingflow_mcp/tools/record_tools.py +255 -2
- package/src/qingflow_mcp/tools/repository_dev_tools.py +21 -2
- package/src/qingflow_mcp/tools/resource_read_tools.py +23 -1
- package/src/qingflow_mcp/tools/role_tools.py +19 -1
- package/src/qingflow_mcp/tools/solution_tools.py +56 -1
- package/src/qingflow_mcp/tools/task_context_tools.py +72 -1
- package/src/qingflow_mcp/tools/task_tools.py +49 -3
- package/src/qingflow_mcp/tools/view_tools.py +56 -1
- package/src/qingflow_mcp/tools/workflow_tools.py +65 -1
- package/src/qingflow_mcp/tools/workspace_tools.py +100 -217
|
@@ -6,11 +6,21 @@ from ..backend_client import BackendRequestContext
|
|
|
6
6
|
from ..config import DEFAULT_PROFILE
|
|
7
7
|
from ..errors import QingflowApiError, raise_tool_error
|
|
8
8
|
from ..json_types import JSONObject, JSONValue
|
|
9
|
-
from .base import ToolBase
|
|
9
|
+
from .base import ToolBase, tool_cn_name
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class WorkflowTools(ToolBase):
|
|
13
|
+
"""流程工具(中文名:流程节点与规则管理)。
|
|
14
|
+
|
|
15
|
+
类型:流程配置工具。
|
|
16
|
+
主要职责:
|
|
17
|
+
1. 查询流程节点与流程图配置;
|
|
18
|
+
2. 读取与更新流程规则;
|
|
19
|
+
3. 支持流程发布与流程调试辅助能力。
|
|
20
|
+
"""
|
|
21
|
+
|
|
13
22
|
def register(self, mcp: FastMCP) -> None:
|
|
23
|
+
"""注册当前工具到 MCP 服务。"""
|
|
14
24
|
@mcp.tool()
|
|
15
25
|
def workflow_list_nodes(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
16
26
|
return self.workflow_list_nodes(profile=profile, app_key=app_key)
|
|
@@ -59,16 +69,22 @@ class WorkflowTools(ToolBase):
|
|
|
59
69
|
def workflow_get_print_nodes(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
60
70
|
return self.workflow_get_print_nodes(profile=profile, app_key=app_key)
|
|
61
71
|
|
|
72
|
+
@tool_cn_name("流程节点列表")
|
|
62
73
|
def workflow_list_nodes(self, *, profile: str, app_key: str) -> JSONObject:
|
|
74
|
+
"""执行流程相关逻辑。"""
|
|
63
75
|
self._require_app_key(app_key)
|
|
64
76
|
return self._request(profile, "GET", f"/app/{app_key}/auditNodes", app_key=app_key)
|
|
65
77
|
|
|
78
|
+
@tool_cn_name("流程节点详情")
|
|
66
79
|
def workflow_get_node_detail(self, *, profile: str, app_key: str, audit_node_id: int) -> JSONObject:
|
|
80
|
+
"""执行流程相关逻辑。"""
|
|
67
81
|
self._require_app_key(app_key)
|
|
68
82
|
self._require_positive("audit_node_id", audit_node_id)
|
|
69
83
|
return self._request(profile, "GET", f"/app/{app_key}/auditNodes/{audit_node_id}", app_key=app_key, audit_node_id=audit_node_id)
|
|
70
84
|
|
|
85
|
+
@tool_cn_name("新增流程节点")
|
|
71
86
|
def workflow_add_node(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
87
|
+
"""执行流程相关逻辑。"""
|
|
72
88
|
self._require_app_key(app_key)
|
|
73
89
|
body = self._require_dict(payload)
|
|
74
90
|
return self._request_with_post_fallbacks(
|
|
@@ -79,7 +95,9 @@ class WorkflowTools(ToolBase):
|
|
|
79
95
|
alternate_paths=[f"/app/{app_key}/auditNode"],
|
|
80
96
|
)
|
|
81
97
|
|
|
98
|
+
@tool_cn_name("更新流程节点")
|
|
82
99
|
def workflow_update_node(self, *, profile: str, app_key: str, audit_node_id: int, payload: JSONObject) -> JSONObject:
|
|
100
|
+
"""执行流程相关逻辑。"""
|
|
83
101
|
self._require_app_key(app_key)
|
|
84
102
|
self._require_positive("audit_node_id", audit_node_id)
|
|
85
103
|
body = self._require_dict(payload)
|
|
@@ -94,36 +112,50 @@ class WorkflowTools(ToolBase):
|
|
|
94
112
|
audit_node_id=audit_node_id,
|
|
95
113
|
)
|
|
96
114
|
|
|
115
|
+
@tool_cn_name("删除流程节点")
|
|
97
116
|
def workflow_delete_node(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
117
|
+
"""执行流程相关逻辑。"""
|
|
98
118
|
self._require_app_key(app_key)
|
|
99
119
|
body = self._require_dict(payload)
|
|
100
120
|
return self._request(profile, "DELETE", f"/app/{app_key}/auditNode", app_key=app_key, json_body=body, risk_operation="delete", risk_target="workflow node configuration")
|
|
101
121
|
|
|
122
|
+
@tool_cn_name("复制粘贴流程节点")
|
|
102
123
|
def workflow_copy_paste_node(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
124
|
+
"""执行流程相关逻辑。"""
|
|
103
125
|
self._require_app_key(app_key)
|
|
104
126
|
body = self._require_dict(payload)
|
|
105
127
|
return self._request(profile, "POST", f"/app/{app_key}/auditNode/copyAndPaste", app_key=app_key, json_body=body)
|
|
106
128
|
|
|
129
|
+
@tool_cn_name("剪切粘贴流程节点")
|
|
107
130
|
def workflow_cut_paste_node(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
131
|
+
"""执行流程相关逻辑。"""
|
|
108
132
|
self._require_app_key(app_key)
|
|
109
133
|
body = self._require_dict(payload)
|
|
110
134
|
return self._request(profile, "POST", f"/app/{app_key}/auditNode/cutAndPaste", app_key=app_key, json_body=body)
|
|
111
135
|
|
|
136
|
+
@tool_cn_name("创建流程分支")
|
|
112
137
|
def workflow_create_sub_branch(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
138
|
+
"""执行流程相关逻辑。"""
|
|
113
139
|
self._require_app_key(app_key)
|
|
114
140
|
body = self._require_dict(payload)
|
|
115
141
|
return self._request(profile, "POST", f"/app/{app_key}/auditNode/subBranch", app_key=app_key, json_body=body)
|
|
116
142
|
|
|
143
|
+
@tool_cn_name("删除流程分支")
|
|
117
144
|
def workflow_delete_sub_branch(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
145
|
+
"""执行流程相关逻辑。"""
|
|
118
146
|
self._require_app_key(app_key)
|
|
119
147
|
body = self._require_dict(payload)
|
|
120
148
|
return self._request(profile, "DELETE", f"/app/{app_key}/auditNode/subBranch", app_key=app_key, json_body=body, risk_operation="delete", risk_target="workflow branch configuration")
|
|
121
149
|
|
|
150
|
+
@tool_cn_name("流程全局设置")
|
|
122
151
|
def workflow_get_global_settings(self, *, profile: str, app_key: str) -> JSONObject:
|
|
152
|
+
"""执行流程相关逻辑。"""
|
|
123
153
|
self._require_app_key(app_key)
|
|
124
154
|
return self._request(profile, "GET", f"/app/{app_key}/workflow/global/setting", app_key=app_key)
|
|
125
155
|
|
|
156
|
+
@tool_cn_name("更新流程全局设置")
|
|
126
157
|
def workflow_update_global_settings(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
158
|
+
"""执行流程相关逻辑。"""
|
|
127
159
|
self._require_app_key(app_key)
|
|
128
160
|
body = self._require_dict(payload)
|
|
129
161
|
return self._request_with_post_fallbacks(
|
|
@@ -136,7 +168,9 @@ class WorkflowTools(ToolBase):
|
|
|
136
168
|
risk_target="workflow global settings",
|
|
137
169
|
)
|
|
138
170
|
|
|
171
|
+
@tool_cn_name("发布流程")
|
|
139
172
|
def workflow_publish(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
173
|
+
"""执行流程相关逻辑。"""
|
|
140
174
|
self._require_app_key(app_key)
|
|
141
175
|
body = self._require_dict(payload)
|
|
142
176
|
return self._request_with_post_fallbacks(
|
|
@@ -147,11 +181,14 @@ class WorkflowTools(ToolBase):
|
|
|
147
181
|
alternate_paths=[],
|
|
148
182
|
)
|
|
149
183
|
|
|
184
|
+
@tool_cn_name("流程后续节点")
|
|
150
185
|
def workflow_get_future_nodes(self, *, profile: str, app_key: str, apply_id: int) -> JSONObject:
|
|
186
|
+
"""执行流程相关逻辑。"""
|
|
151
187
|
self._require_app_key(app_key)
|
|
152
188
|
self._require_positive("apply_id", apply_id)
|
|
153
189
|
return self._request(profile, "GET", f"/app/{app_key}/auditNode/futureList/{apply_id}", app_key=app_key, apply_id=apply_id)
|
|
154
190
|
|
|
191
|
+
@tool_cn_name("流程后续节点(应用)")
|
|
155
192
|
def workflow_get_future_nodes_app(
|
|
156
193
|
self,
|
|
157
194
|
*,
|
|
@@ -161,6 +198,7 @@ class WorkflowTools(ToolBase):
|
|
|
161
198
|
role: int,
|
|
162
199
|
audit_node_id: int | None,
|
|
163
200
|
) -> JSONObject:
|
|
201
|
+
"""执行流程相关逻辑。"""
|
|
164
202
|
self._require_app_key(app_key)
|
|
165
203
|
self._require_positive("apply_id", apply_id)
|
|
166
204
|
params: JSONObject = {"role": role}
|
|
@@ -176,52 +214,72 @@ class WorkflowTools(ToolBase):
|
|
|
176
214
|
params=params,
|
|
177
215
|
)
|
|
178
216
|
|
|
217
|
+
@tool_cn_name("流程 Webhook 测试")
|
|
179
218
|
def workflow_webhook_test(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
219
|
+
"""执行流程相关逻辑。"""
|
|
180
220
|
self._require_app_key(app_key)
|
|
181
221
|
body = self._require_dict(payload)
|
|
182
222
|
return self._request(profile, "POST", f"/app/{app_key}/auditNode/webhookTest", app_key=app_key, json_body=body)
|
|
183
223
|
|
|
224
|
+
@tool_cn_name("流程 QSource 查询")
|
|
184
225
|
def workflow_qsource_query(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
226
|
+
"""执行流程相关逻辑。"""
|
|
185
227
|
self._require_app_key(app_key)
|
|
186
228
|
body = self._require_dict(payload)
|
|
187
229
|
return self._request(profile, "POST", f"/app/{app_key}/auditNode/qSourceQuery", app_key=app_key, json_body=body)
|
|
188
230
|
|
|
231
|
+
@tool_cn_name("流程 QSource 测试")
|
|
189
232
|
def workflow_qsource_test(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
233
|
+
"""执行流程相关逻辑。"""
|
|
190
234
|
self._require_app_key(app_key)
|
|
191
235
|
body = self._require_dict(payload)
|
|
192
236
|
return self._request(profile, "POST", f"/app/{app_key}/auditNode/qSourceQueryTest", app_key=app_key, json_body=body)
|
|
193
237
|
|
|
238
|
+
@tool_cn_name("流程主动 QSource 配置")
|
|
194
239
|
def workflow_get_qsource_active(self, *, profile: str, app_key: str, qsource_id: int) -> JSONObject:
|
|
240
|
+
"""执行流程相关逻辑。"""
|
|
195
241
|
self._require_app_key(app_key)
|
|
196
242
|
self._require_positive("qsource_id", qsource_id)
|
|
197
243
|
return self._request(profile, "GET", f"/app/{app_key}/auditNode/active/qsource", app_key=app_key, params={"qSourceId": qsource_id})
|
|
198
244
|
|
|
245
|
+
@tool_cn_name("更新主动 QSource 配置")
|
|
199
246
|
def workflow_upsert_qsource_active(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
247
|
+
"""执行流程相关逻辑。"""
|
|
200
248
|
self._require_app_key(app_key)
|
|
201
249
|
body = self._require_dict(payload)
|
|
202
250
|
return self._request(profile, "POST", f"/app/{app_key}/auditNode/active/qsource", app_key=app_key, json_body=body)
|
|
203
251
|
|
|
252
|
+
@tool_cn_name("流程被动 QSource 配置")
|
|
204
253
|
def workflow_get_qsource_passive(self, *, profile: str, app_key: str, qsource_id: int) -> JSONObject:
|
|
254
|
+
"""执行流程相关逻辑。"""
|
|
205
255
|
self._require_app_key(app_key)
|
|
206
256
|
self._require_positive("qsource_id", qsource_id)
|
|
207
257
|
return self._request(profile, "GET", f"/app/{app_key}/auditNode/passive/qsource", app_key=app_key, params={"qSourceId": qsource_id})
|
|
208
258
|
|
|
259
|
+
@tool_cn_name("更新被动 QSource 配置")
|
|
209
260
|
def workflow_upsert_qsource_passive(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
261
|
+
"""执行流程相关逻辑。"""
|
|
210
262
|
self._require_app_key(app_key)
|
|
211
263
|
body = self._require_dict(payload)
|
|
212
264
|
return self._request(profile, "POST", f"/app/{app_key}/auditNode/passive/qsource", app_key=app_key, json_body=body)
|
|
213
265
|
|
|
266
|
+
@tool_cn_name("切换 QSource 状态")
|
|
214
267
|
def workflow_switch_qsource_status(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
268
|
+
"""执行流程相关逻辑。"""
|
|
215
269
|
self._require_app_key(app_key)
|
|
216
270
|
body = self._require_dict(payload)
|
|
217
271
|
return self._request(profile, "POST", f"/app/{app_key}/auditNode/source/status", app_key=app_key, json_body=body)
|
|
218
272
|
|
|
273
|
+
@tool_cn_name("删除 QSource 配置")
|
|
219
274
|
def workflow_delete_qsource(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
275
|
+
"""执行流程相关逻辑。"""
|
|
220
276
|
self._require_app_key(app_key)
|
|
221
277
|
body = self._require_dict(payload)
|
|
222
278
|
return self._request(profile, "DELETE", f"/app/{app_key}/auditNode/qsource", app_key=app_key, json_body=body, risk_operation="delete", risk_target="workflow qsource configuration")
|
|
223
279
|
|
|
280
|
+
@tool_cn_name("节点可编辑字段")
|
|
224
281
|
def workflow_get_editable_question_ids(self, *, profile: str, app_key: str, audit_node_id: int) -> JSONObject:
|
|
282
|
+
"""执行流程相关逻辑。"""
|
|
225
283
|
self._require_app_key(app_key)
|
|
226
284
|
self._require_positive("audit_node_id", audit_node_id)
|
|
227
285
|
return self._request(
|
|
@@ -232,7 +290,9 @@ class WorkflowTools(ToolBase):
|
|
|
232
290
|
audit_node_id=audit_node_id,
|
|
233
291
|
)
|
|
234
292
|
|
|
293
|
+
@tool_cn_name("流程打印节点")
|
|
235
294
|
def workflow_get_print_nodes(self, *, profile: str, app_key: str) -> JSONObject:
|
|
295
|
+
"""执行流程相关逻辑。"""
|
|
236
296
|
self._require_app_key(app_key)
|
|
237
297
|
return self._request(profile, "GET", f"/app/{app_key}/auditNode/printNodes", app_key=app_key)
|
|
238
298
|
|
|
@@ -249,6 +309,7 @@ class WorkflowTools(ToolBase):
|
|
|
249
309
|
risk_target: str | None = None,
|
|
250
310
|
**extra: JSONValue,
|
|
251
311
|
) -> JSONObject:
|
|
312
|
+
"""执行内部辅助逻辑。"""
|
|
252
313
|
def runner(session_profile, context):
|
|
253
314
|
result = self.backend.request(method, context, path, json_body=json_body, params=params)
|
|
254
315
|
response: JSONObject = {"profile": profile, "ws_id": session_profile.selected_ws_id, "app_key": app_key, "result": result}
|
|
@@ -271,6 +332,7 @@ class WorkflowTools(ToolBase):
|
|
|
271
332
|
risk_target: str | None = None,
|
|
272
333
|
**extra: JSONValue,
|
|
273
334
|
) -> JSONObject:
|
|
335
|
+
"""执行内部辅助逻辑。"""
|
|
274
336
|
def runner(session_profile, context):
|
|
275
337
|
attempted_contexts = [context]
|
|
276
338
|
if context.qf_version is not None:
|
|
@@ -304,9 +366,11 @@ class WorkflowTools(ToolBase):
|
|
|
304
366
|
return self._run(profile, runner)
|
|
305
367
|
|
|
306
368
|
def _require_app_key(self, app_key: str) -> None:
|
|
369
|
+
"""执行内部辅助逻辑。"""
|
|
307
370
|
if not app_key:
|
|
308
371
|
raise_tool_error(QingflowApiError.config_error("app_key is required"))
|
|
309
372
|
|
|
310
373
|
def _require_positive(self, field_name: str, value: int) -> None:
|
|
374
|
+
"""执行内部辅助逻辑。"""
|
|
311
375
|
if value <= 0:
|
|
312
376
|
raise_tool_error(QingflowApiError.config_error(f"{field_name} must be positive"))
|
|
@@ -7,11 +7,20 @@ from mcp.server.fastmcp import FastMCP
|
|
|
7
7
|
from ..backend_client import BackendRequestContext
|
|
8
8
|
from ..config import DEFAULT_PROFILE
|
|
9
9
|
from ..errors import QingflowApiError, raise_tool_error
|
|
10
|
-
from .base import ToolBase
|
|
10
|
+
from .base import ToolBase, tool_cn_name
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class WorkspaceTools(ToolBase):
|
|
14
|
+
"""工作区工具(中文名:工作区与插件管理)。
|
|
15
|
+
|
|
16
|
+
类型:基础上下文工具。
|
|
17
|
+
主要职责:
|
|
18
|
+
1. 查询当前用户可见工作区列表;
|
|
19
|
+
2. 管理工作区插件安装状态。
|
|
20
|
+
"""
|
|
21
|
+
|
|
14
22
|
def register(self, mcp: FastMCP) -> None:
|
|
23
|
+
"""注册当前工具到 MCP 服务。"""
|
|
15
24
|
@mcp.tool()
|
|
16
25
|
def workspace_list(
|
|
17
26
|
profile: str = DEFAULT_PROFILE,
|
|
@@ -27,8 +36,14 @@ class WorkspaceTools(ToolBase):
|
|
|
27
36
|
)
|
|
28
37
|
|
|
29
38
|
@mcp.tool()
|
|
30
|
-
def
|
|
31
|
-
|
|
39
|
+
def workspace_get(
|
|
40
|
+
profile: str = DEFAULT_PROFILE,
|
|
41
|
+
ws_id: int = 0,
|
|
42
|
+
) -> dict[str, Any]:
|
|
43
|
+
return self.workspace_get(
|
|
44
|
+
profile=profile,
|
|
45
|
+
ws_id=ws_id if ws_id > 0 else None,
|
|
46
|
+
)
|
|
32
47
|
|
|
33
48
|
@mcp.tool()
|
|
34
49
|
def workspace_set_plugin_status(
|
|
@@ -42,6 +57,7 @@ class WorkspaceTools(ToolBase):
|
|
|
42
57
|
being_installed=being_installed,
|
|
43
58
|
)
|
|
44
59
|
|
|
60
|
+
@tool_cn_name("工作区列表")
|
|
45
61
|
def workspace_list(
|
|
46
62
|
self,
|
|
47
63
|
*,
|
|
@@ -50,6 +66,7 @@ class WorkspaceTools(ToolBase):
|
|
|
50
66
|
page_size: int = 20,
|
|
51
67
|
include_external: bool = False,
|
|
52
68
|
) -> dict[str, Any]:
|
|
69
|
+
"""执行工作区相关逻辑。"""
|
|
53
70
|
if page_num <= 0 or page_size <= 0:
|
|
54
71
|
raise_tool_error(QingflowApiError.config_error("page_num and page_size must be positive"))
|
|
55
72
|
|
|
@@ -86,226 +103,32 @@ class WorkspaceTools(ToolBase):
|
|
|
86
103
|
|
|
87
104
|
return self._run(profile, runner, require_workspace=False)
|
|
88
105
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
try:
|
|
105
|
-
result = self.backend.request("GET", call_context, f"/user/workspace/{ws_id}")
|
|
106
|
-
except QingflowApiError as e:
|
|
107
|
-
if e.http_status == 404:
|
|
108
|
-
result = self._workspace_from_list(call_context, ws_id) or {"wsId": ws_id, "workspaceName": f"Workspace {ws_id}"}
|
|
109
|
-
else:
|
|
110
|
-
raise
|
|
111
|
-
|
|
112
|
-
if isinstance(result, dict):
|
|
113
|
-
ws_name = (
|
|
114
|
-
str(result.get("workspaceName") or result.get("wsName") or "").strip()
|
|
115
|
-
or None
|
|
116
|
-
)
|
|
117
|
-
if ws_name is None:
|
|
118
|
-
fallback_workspace = self._workspace_from_list(call_context, ws_id)
|
|
119
|
-
if isinstance(fallback_workspace, dict):
|
|
120
|
-
result = fallback_workspace
|
|
121
|
-
ws_name = (
|
|
122
|
-
str(result.get("workspaceName") or result.get("wsName") or "").strip()
|
|
123
|
-
or None
|
|
124
|
-
)
|
|
125
|
-
else:
|
|
126
|
-
ws_name = None
|
|
127
|
-
if ws_name is None:
|
|
128
|
-
ws_name = f"Workspace {ws_id}"
|
|
129
|
-
session_profile = self.sessions.select_workspace(profile, ws_id=ws_id, ws_name=ws_name)
|
|
130
|
-
workspace_qf_version = self._workspace_system_version(result)
|
|
131
|
-
should_refresh_route = (
|
|
132
|
-
workspace_qf_version is not None
|
|
133
|
-
and (context.qf_version_source or "unset") != "explicit"
|
|
134
|
-
and workspace_qf_version != session_profile.qf_version
|
|
135
|
-
)
|
|
136
|
-
if should_refresh_route:
|
|
137
|
-
session_profile = self.sessions.update_route(
|
|
138
|
-
profile,
|
|
139
|
-
qf_version=workspace_qf_version,
|
|
140
|
-
qf_version_source="workspace_system_version",
|
|
141
|
-
)
|
|
142
|
-
active_context = BackendRequestContext(
|
|
143
|
-
base_url=session_profile.base_url,
|
|
144
|
-
token=context.token,
|
|
145
|
-
ws_id=session_profile.selected_ws_id,
|
|
146
|
-
qf_version=session_profile.qf_version,
|
|
147
|
-
qf_version_source=session_profile.qf_version_source,
|
|
148
|
-
)
|
|
149
|
-
workspace_version = self._workspace_version_summary(
|
|
150
|
-
self._fetch_workspace_account_info(active_context),
|
|
151
|
-
workspace_base_info=self._fetch_workspace_base_info(active_context),
|
|
152
|
-
workspace_detail=result,
|
|
153
|
-
)
|
|
106
|
+
@tool_cn_name("工作区详情")
|
|
107
|
+
def workspace_get(
|
|
108
|
+
self,
|
|
109
|
+
*,
|
|
110
|
+
profile: str = DEFAULT_PROFILE,
|
|
111
|
+
ws_id: int | None = None,
|
|
112
|
+
) -> dict[str, Any]:
|
|
113
|
+
"""读取单个工作区详情,并尽量补齐真实 systemVersion。"""
|
|
114
|
+
|
|
115
|
+
def runner(session_profile, context):
|
|
116
|
+
target_ws_id = ws_id or (session_profile.selected_ws_id if session_profile is not None else None)
|
|
117
|
+
if target_ws_id is None or target_ws_id <= 0:
|
|
118
|
+
raise_tool_error(QingflowApiError.workspace_not_selected(profile))
|
|
119
|
+
workspace = self._fetch_workspace_with_fallback(context, ws_id=target_ws_id)
|
|
120
|
+
system_version = self._workspace_system_version(workspace)
|
|
154
121
|
return {
|
|
155
122
|
"profile": profile,
|
|
156
|
-
"
|
|
157
|
-
"
|
|
158
|
-
"
|
|
159
|
-
"
|
|
160
|
-
"qf_version": session_profile.qf_version,
|
|
161
|
-
"qf_version_source": session_profile.qf_version_source,
|
|
162
|
-
"request_route": self.backend.describe_route(
|
|
163
|
-
active_context
|
|
164
|
-
),
|
|
123
|
+
"ws_id": target_ws_id,
|
|
124
|
+
"qf_version": system_version,
|
|
125
|
+
"qf_version_source": "workspace_system_version" if system_version else "unverified",
|
|
126
|
+
"workspace": workspace,
|
|
165
127
|
}
|
|
166
128
|
|
|
167
129
|
return self._run(profile, runner, require_workspace=False)
|
|
168
130
|
|
|
169
|
-
|
|
170
|
-
try:
|
|
171
|
-
payload = self.backend.request(
|
|
172
|
-
"POST",
|
|
173
|
-
context,
|
|
174
|
-
"/user/workspaceList/pageQuery",
|
|
175
|
-
json_body={"pageNum": 1, "pageSize": 100, "authList": [0, 1, 2]},
|
|
176
|
-
)
|
|
177
|
-
except Exception:
|
|
178
|
-
return None
|
|
179
|
-
workspaces = payload.get("list", []) if isinstance(payload, dict) else []
|
|
180
|
-
found = next(
|
|
181
|
-
(
|
|
182
|
-
item
|
|
183
|
-
for item in workspaces
|
|
184
|
-
if isinstance(item, dict) and item.get("wsId") == ws_id
|
|
185
|
-
),
|
|
186
|
-
None,
|
|
187
|
-
)
|
|
188
|
-
return found if isinstance(found, dict) else None
|
|
189
|
-
|
|
190
|
-
def _workspace_system_version(self, workspace: Any) -> str | None:
|
|
191
|
-
if not isinstance(workspace, dict):
|
|
192
|
-
return None
|
|
193
|
-
value = workspace.get("systemVersion")
|
|
194
|
-
if value is None:
|
|
195
|
-
return None
|
|
196
|
-
normalized = str(value).strip()
|
|
197
|
-
return normalized or None
|
|
198
|
-
|
|
199
|
-
def _fetch_workspace_base_info(self, context: BackendRequestContext) -> dict[str, Any] | None:
|
|
200
|
-
try:
|
|
201
|
-
payload = self.backend.request("GET", context, "/ws/baseInfo")
|
|
202
|
-
except QingflowApiError:
|
|
203
|
-
return None
|
|
204
|
-
return payload if isinstance(payload, dict) else None
|
|
205
|
-
|
|
206
|
-
def _fetch_workspace_account_info(self, context: BackendRequestContext) -> dict[str, Any] | None:
|
|
207
|
-
try:
|
|
208
|
-
payload = self.backend.request("GET", context, "/ws/account")
|
|
209
|
-
except QingflowApiError:
|
|
210
|
-
return None
|
|
211
|
-
return payload if isinstance(payload, dict) else None
|
|
212
|
-
|
|
213
|
-
def _workspace_version_summary(
|
|
214
|
-
self,
|
|
215
|
-
payload: Any,
|
|
216
|
-
*,
|
|
217
|
-
workspace_base_info: Any,
|
|
218
|
-
workspace_detail: Any,
|
|
219
|
-
) -> dict[str, Any]:
|
|
220
|
-
account_info = payload if isinstance(payload, dict) else {}
|
|
221
|
-
base_info = workspace_base_info if isinstance(workspace_base_info, dict) else {}
|
|
222
|
-
detail_info = workspace_detail if isinstance(workspace_detail, dict) else {}
|
|
223
|
-
level_code = self._first_present_int(
|
|
224
|
-
account_info.get("accountLevel"),
|
|
225
|
-
base_info.get("accountLevel"),
|
|
226
|
-
detail_info.get("accountLevel"),
|
|
227
|
-
)
|
|
228
|
-
level_name = self._account_level_name(level_code)
|
|
229
|
-
return {
|
|
230
|
-
"level_code": level_code,
|
|
231
|
-
"level_name": level_name,
|
|
232
|
-
"display_name": self._account_level_display_name(level_name),
|
|
233
|
-
"being_trial": self._first_present_bool(
|
|
234
|
-
account_info.get("trial"),
|
|
235
|
-
base_info.get("trial"),
|
|
236
|
-
detail_info.get("trial"),
|
|
237
|
-
),
|
|
238
|
-
"expire_date": self._first_present_value(
|
|
239
|
-
account_info.get("expireDate"),
|
|
240
|
-
base_info.get("expireDate"),
|
|
241
|
-
detail_info.get("expireDate"),
|
|
242
|
-
),
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
def _account_level_name(self, level_code: int | None) -> str | None:
|
|
246
|
-
mapping = {
|
|
247
|
-
0: "FREE",
|
|
248
|
-
10: "AIR",
|
|
249
|
-
20: "BASIC",
|
|
250
|
-
30: "TEAM",
|
|
251
|
-
35: "PROFESSIONAL",
|
|
252
|
-
40: "ENTERPRISE",
|
|
253
|
-
}
|
|
254
|
-
return mapping.get(level_code)
|
|
255
|
-
|
|
256
|
-
def _account_level_display_name(self, level_name: str | None) -> str | None:
|
|
257
|
-
mapping = {
|
|
258
|
-
"FREE": "免费版",
|
|
259
|
-
"AIR": "Air版",
|
|
260
|
-
"BASIC": "Pro版",
|
|
261
|
-
"TEAM": "团队版",
|
|
262
|
-
"PROFESSIONAL": "专业版",
|
|
263
|
-
"ENTERPRISE": "企业版",
|
|
264
|
-
}
|
|
265
|
-
return mapping.get(level_name)
|
|
266
|
-
|
|
267
|
-
def _coerce_int(self, value: Any) -> int | None:
|
|
268
|
-
if isinstance(value, bool) or value is None:
|
|
269
|
-
return None
|
|
270
|
-
if isinstance(value, int):
|
|
271
|
-
return value
|
|
272
|
-
try:
|
|
273
|
-
return int(str(value).strip())
|
|
274
|
-
except (TypeError, ValueError):
|
|
275
|
-
return None
|
|
276
|
-
|
|
277
|
-
def _coerce_bool(self, value: Any) -> bool | None:
|
|
278
|
-
if isinstance(value, bool):
|
|
279
|
-
return value
|
|
280
|
-
if value is None:
|
|
281
|
-
return None
|
|
282
|
-
normalized = str(value).strip().lower()
|
|
283
|
-
if normalized in {"true", "1"}:
|
|
284
|
-
return True
|
|
285
|
-
if normalized in {"false", "0"}:
|
|
286
|
-
return False
|
|
287
|
-
return None
|
|
288
|
-
|
|
289
|
-
def _first_present_int(self, *values: Any) -> int | None:
|
|
290
|
-
for value in values:
|
|
291
|
-
coerced = self._coerce_int(value)
|
|
292
|
-
if coerced is not None:
|
|
293
|
-
return coerced
|
|
294
|
-
return None
|
|
295
|
-
|
|
296
|
-
def _first_present_bool(self, *values: Any) -> bool | None:
|
|
297
|
-
for value in values:
|
|
298
|
-
coerced = self._coerce_bool(value)
|
|
299
|
-
if coerced is not None:
|
|
300
|
-
return coerced
|
|
301
|
-
return None
|
|
302
|
-
|
|
303
|
-
def _first_present_value(self, *values: Any) -> Any:
|
|
304
|
-
for value in values:
|
|
305
|
-
if value is not None:
|
|
306
|
-
return value
|
|
307
|
-
return None
|
|
308
|
-
|
|
131
|
+
@tool_cn_name("设置工作区插件状态")
|
|
309
132
|
def workspace_set_plugin_status(
|
|
310
133
|
self,
|
|
311
134
|
*,
|
|
@@ -313,6 +136,7 @@ class WorkspaceTools(ToolBase):
|
|
|
313
136
|
plugin_id: int,
|
|
314
137
|
being_installed: bool = True,
|
|
315
138
|
) -> dict[str, Any]:
|
|
139
|
+
"""执行工作区相关逻辑。"""
|
|
316
140
|
if plugin_id <= 0:
|
|
317
141
|
raise_tool_error(QingflowApiError.config_error("plugin_id must be positive"))
|
|
318
142
|
|
|
@@ -334,3 +158,62 @@ class WorkspaceTools(ToolBase):
|
|
|
334
158
|
}
|
|
335
159
|
|
|
336
160
|
return self._run(profile, runner)
|
|
161
|
+
|
|
162
|
+
def _fetch_workspace_with_fallback(
|
|
163
|
+
self,
|
|
164
|
+
context: BackendRequestContext,
|
|
165
|
+
*,
|
|
166
|
+
ws_id: int,
|
|
167
|
+
) -> dict[str, Any]:
|
|
168
|
+
workspace = self.backend.request("GET", context, f"/user/workspace/{ws_id}")
|
|
169
|
+
if not isinstance(workspace, dict):
|
|
170
|
+
raise_tool_error(QingflowApiError(category="workspace", message=f"Workspace {ws_id} is not accessible"))
|
|
171
|
+
if self._workspace_needs_list_fallback(workspace):
|
|
172
|
+
fallback = self._fetch_workspace_from_list(context, ws_id=ws_id)
|
|
173
|
+
if isinstance(fallback, dict):
|
|
174
|
+
merged = dict(workspace)
|
|
175
|
+
for key, value in fallback.items():
|
|
176
|
+
if merged.get(key) in (None, "") and value not in (None, ""):
|
|
177
|
+
merged[key] = value
|
|
178
|
+
workspace = merged
|
|
179
|
+
return workspace
|
|
180
|
+
|
|
181
|
+
def _fetch_workspace_from_list(self, context: BackendRequestContext, *, ws_id: int) -> dict[str, Any] | None:
|
|
182
|
+
payload = self.backend.request(
|
|
183
|
+
"POST",
|
|
184
|
+
BackendRequestContext(
|
|
185
|
+
base_url=context.base_url,
|
|
186
|
+
token=context.token,
|
|
187
|
+
ws_id=None,
|
|
188
|
+
qf_version=context.qf_version,
|
|
189
|
+
qf_version_source=context.qf_version_source,
|
|
190
|
+
),
|
|
191
|
+
"/user/workspaceList/pageQuery",
|
|
192
|
+
json_body={"pageNum": 1, "pageSize": 100, "authList": [0, 1, 2, 3]},
|
|
193
|
+
)
|
|
194
|
+
workspaces = payload.get("list") if isinstance(payload, dict) else []
|
|
195
|
+
if not isinstance(workspaces, list):
|
|
196
|
+
return None
|
|
197
|
+
found = next(
|
|
198
|
+
(
|
|
199
|
+
item
|
|
200
|
+
for item in workspaces
|
|
201
|
+
if isinstance(item, dict) and item.get("wsId") == ws_id
|
|
202
|
+
),
|
|
203
|
+
None,
|
|
204
|
+
)
|
|
205
|
+
return found if isinstance(found, dict) else None
|
|
206
|
+
|
|
207
|
+
def _workspace_needs_list_fallback(self, workspace: dict[str, Any]) -> bool:
|
|
208
|
+
workspace_name = str(workspace.get("workspaceName") or workspace.get("wsName") or "").strip()
|
|
209
|
+
system_version = self._workspace_system_version(workspace)
|
|
210
|
+
return not workspace_name or system_version is None
|
|
211
|
+
|
|
212
|
+
def _workspace_system_version(self, workspace: Any) -> str | None:
|
|
213
|
+
if not isinstance(workspace, dict):
|
|
214
|
+
return None
|
|
215
|
+
value = workspace.get("systemVersion")
|
|
216
|
+
if value is None:
|
|
217
|
+
return None
|
|
218
|
+
normalized = str(value).strip()
|
|
219
|
+
return normalized or None
|