@josephyan/qingflow-app-builder-mcp 1.1.25 → 1.1.26
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 +5 -6
- package/docs/local-agent-install.md +6 -57
- package/entry_point.py +1 -1
- package/npm/bin/qingflow-app-builder-mcp.mjs +33 -2
- package/npm/lib/runtime.mjs +101 -21
- package/npm/scripts/postinstall.mjs +10 -1
- package/package.json +2 -3
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-builder/SKILL.md +42 -104
- package/skills/qingflow-app-builder/references/build-complete-system.md +428 -0
- package/skills/qingflow-app-builder/references/build-single-app.md +530 -0
- package/skills/qingflow-app-builder/references/create-app.md +11 -30
- package/skills/qingflow-app-builder/references/environments.md +1 -1
- package/skills/qingflow-app-builder/references/gotchas.md +9 -31
- package/skills/qingflow-app-builder/references/match-rules.md +0 -15
- package/skills/qingflow-app-builder/references/solution-playbooks.md +0 -10
- package/skills/qingflow-app-builder/references/tool-selection.md +23 -38
- package/skills/qingflow-app-builder/references/update-flow.md +38 -22
- package/skills/qingflow-app-builder/references/update-schema.md +0 -14
- package/skills/qingflow-app-builder/references/update-views.md +14 -83
- package/skills/qingflow-app-builder-code-integrations/SKILL.md +3 -5
- package/skills/qingflow-app-builder-code-integrations/references/code-block.md +1 -1
- package/skills/qingflow-app-builder-code-integrations/references/q-linker.md +1 -1
- package/skills/qingflow-mcp-setup/SKILL.md +111 -0
- package/skills/qingflow-mcp-setup/agents/openai.yaml +4 -0
- package/skills/qingflow-mcp-setup/references/claude-desktop.md +34 -0
- package/skills/qingflow-mcp-setup/references/environments.md +61 -0
- package/skills/qingflow-mcp-setup/references/generic-stdio.md +31 -0
- package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
- package/skills/qingflow-workflow-builder/SKILL.md +96 -0
- package/skills/qingflow-workflow-builder/manifest.yaml +8 -0
- package/skills/qingflow-workflow-builder/references/01-overview.md +45 -0
- package/skills/qingflow-workflow-builder/references/02-update-mode.md +53 -0
- package/skills/qingflow-workflow-builder/references/03-flow-patterns.md +57 -0
- package/skills/qingflow-workflow-builder/references/04-stage1-business-modeling.md +131 -0
- package/skills/qingflow-workflow-builder/references/05-stage2-members-roles.md +29 -0
- package/skills/qingflow-workflow-builder/references/06-stage3-build-spec.md +165 -0
- package/skills/qingflow-workflow-builder/references/07-stage4-validate-spec.md +33 -0
- package/skills/qingflow-workflow-builder/references/08-stage5-apply-verify.md +51 -0
- package/skills/qingflow-workflow-builder/references/09-stage6-summary.md +88 -0
- package/skills/qingflow-workflow-builder/references/10-node-config-reference.md +93 -0
- package/skills/qingflow-workflow-builder/references/11-troubleshooting.md +15 -0
- package/skills/qingflow-workflow-builder/scripts/diff_flow_spec.py +275 -0
- package/skills/qingflow-workflow-builder/scripts/validate_flow_spec.py +605 -0
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/__main__.py +2 -6
- package/src/qingflow_mcp/builder_facade/models.py +108 -485
- package/src/qingflow_mcp/builder_facade/service.py +1686 -6674
- package/src/qingflow_mcp/cli/commands/builder.py +364 -322
- package/src/qingflow_mcp/cli/commands/chart.py +1 -1
- package/src/qingflow_mcp/cli/commands/common.py +3 -12
- package/src/qingflow_mcp/cli/commands/exports.py +2 -2
- package/src/qingflow_mcp/cli/commands/imports.py +3 -3
- package/src/qingflow_mcp/cli/commands/portal.py +2 -2
- package/src/qingflow_mcp/cli/commands/record.py +27 -101
- package/src/qingflow_mcp/cli/commands/task.py +47 -28
- package/src/qingflow_mcp/cli/commands/view.py +1 -1
- package/src/qingflow_mcp/cli/context.py +3 -0
- package/src/qingflow_mcp/cli/formatters.py +16 -784
- package/src/qingflow_mcp/cli/main.py +33 -117
- package/src/qingflow_mcp/errors.py +2 -43
- package/src/qingflow_mcp/public_surface.py +17 -26
- package/src/qingflow_mcp/response_trim.py +17 -81
- package/src/qingflow_mcp/server.py +12 -14
- package/src/qingflow_mcp/server_app_builder.py +36 -79
- package/src/qingflow_mcp/server_app_user.py +16 -22
- package/src/qingflow_mcp/session_store.py +7 -11
- package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
- package/src/qingflow_mcp/solution/executor.py +18 -245
- package/src/qingflow_mcp/tools/ai_builder_tools.py +1396 -3103
- package/src/qingflow_mcp/tools/app_tools.py +43 -184
- package/src/qingflow_mcp/tools/approval_tools.py +35 -197
- package/src/qingflow_mcp/tools/auth_tools.py +16 -92
- package/src/qingflow_mcp/tools/code_block_tools.py +40 -298
- package/src/qingflow_mcp/tools/custom_button_tools.py +10 -64
- package/src/qingflow_mcp/tools/directory_tools.py +72 -236
- package/src/qingflow_mcp/tools/export_tools.py +34 -244
- package/src/qingflow_mcp/tools/feedback_tools.py +0 -9
- package/src/qingflow_mcp/tools/file_tools.py +3 -9
- package/src/qingflow_mcp/tools/import_tools.py +49 -336
- package/src/qingflow_mcp/tools/navigation_tools.py +12 -91
- package/src/qingflow_mcp/tools/package_tools.py +6 -118
- package/src/qingflow_mcp/tools/portal_tools.py +3 -39
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +7 -116
- package/src/qingflow_mcp/tools/record_tools.py +360 -1145
- package/src/qingflow_mcp/tools/resource_read_tools.py +39 -188
- package/src/qingflow_mcp/tools/role_tools.py +9 -80
- package/src/qingflow_mcp/tools/solution_tools.py +45 -59
- package/src/qingflow_mcp/tools/task_context_tools.py +158 -662
- package/src/qingflow_mcp/tools/task_tools.py +29 -113
- package/src/qingflow_mcp/tools/view_tools.py +3 -106
- package/src/qingflow_mcp/tools/workflow_tools.py +4 -48
- package/src/qingflow_mcp/tools/workspace_tools.py +3 -71
- package/npm/bin/qingflow-skills.mjs +0 -5
- package/skills/qingflow-app-builder/references/complete-system-development-guide.md +0 -172
- package/skills/qingflow-app-builder/references/single-app-development-guide.md +0 -87
- package/skills/qingflow-app-builder/scripts/validate_system_build_summary.py +0 -124
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
|
@@ -9,12 +9,12 @@ from mcp.server.fastmcp import FastMCP
|
|
|
9
9
|
|
|
10
10
|
from ..backend_client import BackendRequestContext
|
|
11
11
|
from ..config import DEFAULT_PROFILE
|
|
12
|
-
from ..errors import QingflowApiError,
|
|
12
|
+
from ..errors import QingflowApiError, raise_tool_error
|
|
13
13
|
from ..id_utils import ids_equal, normalize_positive_id_int, normalize_positive_id_text, stringify_backend_id
|
|
14
14
|
from ..json_types import JSONObject
|
|
15
15
|
from .approval_tools import ApprovalTools, _approval_page_amount, _approval_page_items, _approval_page_total
|
|
16
16
|
from .base import ToolBase, tool_cn_name
|
|
17
|
-
from .qingbi_report_tools import _qingbi_base_url
|
|
17
|
+
from .qingbi_report_tools import _qingbi_base_url
|
|
18
18
|
from .record_tools import (
|
|
19
19
|
FieldIndex,
|
|
20
20
|
LAYOUT_ONLY_QUE_TYPES,
|
|
@@ -38,9 +38,6 @@ from .record_tools import (
|
|
|
38
38
|
from .task_tools import TaskTools, _task_page_amount, _task_page_items, _task_page_total
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
TASK_LOCATOR_MAX_SCAN_PAGES_PER_BOX = 3
|
|
42
|
-
|
|
43
|
-
|
|
44
41
|
class TaskContextTools(ToolBase):
|
|
45
42
|
"""任务上下文工具(中文名:任务上下文与审批执行)。
|
|
46
43
|
|
|
@@ -88,37 +85,33 @@ class TaskContextTools(ToolBase):
|
|
|
88
85
|
page_size=page_size,
|
|
89
86
|
)
|
|
90
87
|
|
|
91
|
-
@mcp.tool(
|
|
92
|
-
description=(
|
|
93
|
-
"Read one workflow task. Prefer task_id from task_list.data.items[].task_id; "
|
|
94
|
-
"task_id is not a row number, list index, record id, or workflow node id."
|
|
95
|
-
)
|
|
96
|
-
)
|
|
88
|
+
@mcp.tool()
|
|
97
89
|
def task_get(
|
|
98
90
|
profile: str = DEFAULT_PROFILE,
|
|
99
91
|
task_id: str = "",
|
|
92
|
+
app_key: str = "",
|
|
93
|
+
record_id: str = "",
|
|
94
|
+
workflow_node_id: int = 0,
|
|
100
95
|
include_candidates: bool = True,
|
|
101
96
|
include_associated_reports: bool = True,
|
|
102
97
|
) -> dict[str, Any]:
|
|
103
98
|
return self.task_get(
|
|
104
99
|
profile=profile,
|
|
105
100
|
task_id=task_id,
|
|
106
|
-
app_key=
|
|
107
|
-
record_id=
|
|
108
|
-
workflow_node_id=
|
|
101
|
+
app_key=app_key,
|
|
102
|
+
record_id=record_id,
|
|
103
|
+
workflow_node_id=workflow_node_id,
|
|
109
104
|
include_candidates=include_candidates,
|
|
110
105
|
include_associated_reports=include_associated_reports,
|
|
111
106
|
)
|
|
112
107
|
|
|
113
|
-
@mcp.tool(
|
|
114
|
-
description=(
|
|
115
|
-
self._high_risk_tool_description(operation="execute", target="workflow task action")
|
|
116
|
-
+ " Pass task_id from task_list.data.items[].task_id. Do not pass a row number, list index, record id, or workflow node id as task_id."
|
|
117
|
-
)
|
|
118
|
-
)
|
|
108
|
+
@mcp.tool(description=self._high_risk_tool_description(operation="execute", target="workflow task action"))
|
|
119
109
|
def task_action_execute(
|
|
120
110
|
profile: str = DEFAULT_PROFILE,
|
|
121
111
|
task_id: str = "",
|
|
112
|
+
app_key: str = "",
|
|
113
|
+
record_id: str = "",
|
|
114
|
+
workflow_node_id: int = 0,
|
|
122
115
|
action: str = "",
|
|
123
116
|
payload: dict[str, Any] | None = None,
|
|
124
117
|
fields: dict[str, Any] | None = None,
|
|
@@ -126,20 +119,21 @@ class TaskContextTools(ToolBase):
|
|
|
126
119
|
return self.task_action_execute(
|
|
127
120
|
profile=profile,
|
|
128
121
|
task_id=task_id,
|
|
122
|
+
app_key=app_key,
|
|
123
|
+
record_id=record_id,
|
|
124
|
+
workflow_node_id=workflow_node_id,
|
|
129
125
|
action=action,
|
|
130
126
|
payload=payload or {},
|
|
131
127
|
fields=fields or {},
|
|
132
128
|
)
|
|
133
129
|
|
|
134
|
-
@mcp.tool(
|
|
135
|
-
description=(
|
|
136
|
-
"Read a task-associated report. Pass task_id from task_list.data.items[].task_id; "
|
|
137
|
-
"task_id is not a row number, list index, record id, or workflow node id."
|
|
138
|
-
)
|
|
139
|
-
)
|
|
130
|
+
@mcp.tool()
|
|
140
131
|
def task_associated_report_detail_get(
|
|
141
132
|
profile: str = DEFAULT_PROFILE,
|
|
142
133
|
task_id: str = "",
|
|
134
|
+
app_key: str = "",
|
|
135
|
+
record_id: str = "",
|
|
136
|
+
workflow_node_id: int = 0,
|
|
143
137
|
report_id: int = 0,
|
|
144
138
|
page: int = 1,
|
|
145
139
|
page_size: int = 20,
|
|
@@ -147,30 +141,28 @@ class TaskContextTools(ToolBase):
|
|
|
147
141
|
return self.task_associated_report_detail_get(
|
|
148
142
|
profile=profile,
|
|
149
143
|
task_id=task_id,
|
|
150
|
-
app_key=
|
|
151
|
-
record_id=
|
|
152
|
-
workflow_node_id=
|
|
144
|
+
app_key=app_key,
|
|
145
|
+
record_id=record_id,
|
|
146
|
+
workflow_node_id=workflow_node_id,
|
|
153
147
|
report_id=report_id,
|
|
154
148
|
page=page,
|
|
155
149
|
page_size=page_size,
|
|
156
150
|
)
|
|
157
151
|
|
|
158
|
-
@mcp.tool(
|
|
159
|
-
description=(
|
|
160
|
-
"Read workflow log for one task context. Pass task_id from task_list.data.items[].task_id; "
|
|
161
|
-
"task_id is not a row number, list index, record id, or workflow node id."
|
|
162
|
-
)
|
|
163
|
-
)
|
|
152
|
+
@mcp.tool()
|
|
164
153
|
def task_workflow_log_get(
|
|
165
154
|
profile: str = DEFAULT_PROFILE,
|
|
166
155
|
task_id: str = "",
|
|
156
|
+
app_key: str = "",
|
|
157
|
+
record_id: str = "",
|
|
158
|
+
workflow_node_id: int = 0,
|
|
167
159
|
) -> dict[str, Any]:
|
|
168
160
|
return self.task_workflow_log_get(
|
|
169
161
|
profile=profile,
|
|
170
162
|
task_id=task_id,
|
|
171
|
-
app_key=
|
|
172
|
-
record_id=
|
|
173
|
-
workflow_node_id=
|
|
163
|
+
app_key=app_key,
|
|
164
|
+
record_id=record_id,
|
|
165
|
+
workflow_node_id=workflow_node_id,
|
|
174
166
|
)
|
|
175
167
|
|
|
176
168
|
@tool_cn_name("任务上下文列表")
|
|
@@ -279,7 +271,6 @@ class TaskContextTools(ToolBase):
|
|
|
279
271
|
resolved_app_key = str(locator["app_key"])
|
|
280
272
|
resolved_record_id = int(locator["record_id"])
|
|
281
273
|
resolved_workflow_node_id = int(locator["workflow_node_id"])
|
|
282
|
-
resolved_task_box = str(locator.get("task_box") or "todo")
|
|
283
274
|
self._require_app_record_and_node(resolved_app_key, resolved_record_id, resolved_workflow_node_id)
|
|
284
275
|
data = self._build_task_context(
|
|
285
276
|
profile=profile,
|
|
@@ -287,12 +278,10 @@ class TaskContextTools(ToolBase):
|
|
|
287
278
|
app_key=resolved_app_key,
|
|
288
279
|
record_id=resolved_record_id,
|
|
289
280
|
workflow_node_id=resolved_workflow_node_id,
|
|
290
|
-
task_box=resolved_task_box,
|
|
291
281
|
include_candidates=include_candidates,
|
|
292
282
|
include_associated_reports=include_associated_reports,
|
|
293
283
|
current_uid=session_profile.uid,
|
|
294
284
|
)
|
|
295
|
-
context_warnings = data.get("warnings") if isinstance(data.get("warnings"), list) else []
|
|
296
285
|
data = self._compact_task_get_context(data)
|
|
297
286
|
task_payload = data.get("task")
|
|
298
287
|
if isinstance(task_payload, dict) and task_id_text is not None:
|
|
@@ -302,7 +291,7 @@ class TaskContextTools(ToolBase):
|
|
|
302
291
|
"ws_id": session_profile.selected_ws_id,
|
|
303
292
|
"ok": True,
|
|
304
293
|
"request_route": self._request_route_payload(context),
|
|
305
|
-
"warnings":
|
|
294
|
+
"warnings": [],
|
|
306
295
|
"output_profile": "normal",
|
|
307
296
|
"data": data,
|
|
308
297
|
}
|
|
@@ -314,7 +303,9 @@ class TaskContextTools(ToolBase):
|
|
|
314
303
|
self,
|
|
315
304
|
*,
|
|
316
305
|
profile: str,
|
|
317
|
-
|
|
306
|
+
app_key: str,
|
|
307
|
+
record_id: Any,
|
|
308
|
+
workflow_node_id: int,
|
|
318
309
|
fields: dict[str, Any] | None = None,
|
|
319
310
|
) -> dict[str, Any]:
|
|
320
311
|
"""执行任务相关逻辑。"""
|
|
@@ -323,7 +314,9 @@ class TaskContextTools(ToolBase):
|
|
|
323
314
|
raise_tool_error(QingflowApiError.config_error("fields is required and must be non-empty for task_save_only"))
|
|
324
315
|
return self.task_action_execute(
|
|
325
316
|
profile=profile,
|
|
326
|
-
|
|
317
|
+
app_key=app_key,
|
|
318
|
+
record_id=record_id,
|
|
319
|
+
workflow_node_id=workflow_node_id,
|
|
327
320
|
action="save_only",
|
|
328
321
|
payload={},
|
|
329
322
|
fields=field_updates,
|
|
@@ -331,34 +324,6 @@ class TaskContextTools(ToolBase):
|
|
|
331
324
|
|
|
332
325
|
@tool_cn_name("执行任务动作")
|
|
333
326
|
def task_action_execute(
|
|
334
|
-
self,
|
|
335
|
-
*,
|
|
336
|
-
profile: str,
|
|
337
|
-
task_id: Any,
|
|
338
|
-
action: str,
|
|
339
|
-
payload: dict[str, Any],
|
|
340
|
-
fields: dict[str, Any] | None = None,
|
|
341
|
-
) -> dict[str, Any]:
|
|
342
|
-
"""执行任务相关逻辑。"""
|
|
343
|
-
if task_id in (None, ""):
|
|
344
|
-
raise_tool_error(
|
|
345
|
-
QingflowApiError.config_error(
|
|
346
|
-
"task_id is required for task_action_execute; get it from task_list.data.items[].task_id"
|
|
347
|
-
)
|
|
348
|
-
)
|
|
349
|
-
|
|
350
|
-
return self._task_action_execute_with_locator(
|
|
351
|
-
profile=profile,
|
|
352
|
-
task_id=task_id,
|
|
353
|
-
app_key="",
|
|
354
|
-
record_id="",
|
|
355
|
-
workflow_node_id=0,
|
|
356
|
-
action=action,
|
|
357
|
-
payload=payload,
|
|
358
|
-
fields=fields,
|
|
359
|
-
)
|
|
360
|
-
|
|
361
|
-
def _task_action_execute_with_locator(
|
|
362
327
|
self,
|
|
363
328
|
*,
|
|
364
329
|
profile: str,
|
|
@@ -370,8 +335,10 @@ class TaskContextTools(ToolBase):
|
|
|
370
335
|
payload: dict[str, Any],
|
|
371
336
|
fields: dict[str, Any] | None = None,
|
|
372
337
|
) -> dict[str, Any]:
|
|
338
|
+
"""执行任务相关逻辑。"""
|
|
373
339
|
if task_id in (None, ""):
|
|
374
340
|
normalize_positive_id_int(record_id, field_name="record_id")
|
|
341
|
+
|
|
375
342
|
normalized_action = (action or "").strip().lower()
|
|
376
343
|
if normalized_action not in {"approve", "reject", "rollback", "transfer", "urge", "save_only"}:
|
|
377
344
|
raise_tool_error(
|
|
@@ -401,63 +368,8 @@ class TaskContextTools(ToolBase):
|
|
|
401
368
|
resolved_record_id = int(locator["record_id"])
|
|
402
369
|
resolved_record_id_text = str(locator["record_id_text"] or "")
|
|
403
370
|
resolved_workflow_node_id = int(locator["workflow_node_id"])
|
|
404
|
-
resolved_task_box = str(locator.get("task_box") or "todo")
|
|
405
371
|
record_id_text = resolved_record_id_text
|
|
406
372
|
self._require_app_record_and_node(resolved_app_key, resolved_record_id, resolved_workflow_node_id)
|
|
407
|
-
if normalized_action == "urge":
|
|
408
|
-
raw = self._execute_task_action(
|
|
409
|
-
profile=profile,
|
|
410
|
-
app_key=resolved_app_key,
|
|
411
|
-
record_id=resolved_record_id,
|
|
412
|
-
workflow_node_id=resolved_workflow_node_id,
|
|
413
|
-
normalized_action=normalized_action,
|
|
414
|
-
payload=body,
|
|
415
|
-
prepared_fields=None,
|
|
416
|
-
)
|
|
417
|
-
verification, verification_warnings = self._verify_task_action_runtime(
|
|
418
|
-
profile=profile,
|
|
419
|
-
context=context,
|
|
420
|
-
app_key=resolved_app_key,
|
|
421
|
-
record_id=resolved_record_id,
|
|
422
|
-
workflow_node_id=resolved_workflow_node_id,
|
|
423
|
-
action=normalized_action,
|
|
424
|
-
before_apply_status=None,
|
|
425
|
-
runtime_baseline=None,
|
|
426
|
-
)
|
|
427
|
-
result = {
|
|
428
|
-
"profile": raw.get("profile", profile),
|
|
429
|
-
"ws_id": raw.get("ws_id", session_profile.selected_ws_id),
|
|
430
|
-
"ok": bool(raw.get("ok", True)),
|
|
431
|
-
"status": "success",
|
|
432
|
-
"error_code": None,
|
|
433
|
-
"action_executed": True,
|
|
434
|
-
"safe_to_retry": False,
|
|
435
|
-
"request_route": raw.get("request_route") or self._request_route_payload(context),
|
|
436
|
-
"warnings": verification_warnings,
|
|
437
|
-
"verification": verification,
|
|
438
|
-
"output_profile": "normal",
|
|
439
|
-
"data": {
|
|
440
|
-
"action": normalized_action,
|
|
441
|
-
"resource": {
|
|
442
|
-
"app_key": resolved_app_key,
|
|
443
|
-
"record_id": record_id_text,
|
|
444
|
-
"workflow_node_id": resolved_workflow_node_id,
|
|
445
|
-
},
|
|
446
|
-
"selection": {"action": normalized_action},
|
|
447
|
-
"result": raw.get("result"),
|
|
448
|
-
"human_review": True,
|
|
449
|
-
"field_update_applied": False,
|
|
450
|
-
},
|
|
451
|
-
}
|
|
452
|
-
if task_id_text is not None:
|
|
453
|
-
result["data"]["resource"]["task_id"] = task_id_text
|
|
454
|
-
return result
|
|
455
|
-
if resolved_task_box != "todo":
|
|
456
|
-
raise_tool_error(
|
|
457
|
-
QingflowApiError.config_error(
|
|
458
|
-
f"task_id={task_id_text or ''} resolved to task_box='{resolved_task_box}', but task_action_execute can only execute current todo tasks"
|
|
459
|
-
)
|
|
460
|
-
)
|
|
461
373
|
try:
|
|
462
374
|
task_context = self._build_task_context(
|
|
463
375
|
profile=profile,
|
|
@@ -465,13 +377,12 @@ class TaskContextTools(ToolBase):
|
|
|
465
377
|
app_key=resolved_app_key,
|
|
466
378
|
record_id=resolved_record_id,
|
|
467
379
|
workflow_node_id=resolved_workflow_node_id,
|
|
468
|
-
task_box=resolved_task_box,
|
|
469
380
|
include_candidates=False,
|
|
470
381
|
include_associated_reports=False,
|
|
471
382
|
current_uid=session_profile.uid,
|
|
472
383
|
)
|
|
473
384
|
except QingflowApiError as error:
|
|
474
|
-
if
|
|
385
|
+
if error.backend_code == 46001:
|
|
475
386
|
return self._task_action_visibility_unverified_response(
|
|
476
387
|
profile=profile,
|
|
477
388
|
session_profile=session_profile,
|
|
@@ -511,7 +422,7 @@ class TaskContextTools(ToolBase):
|
|
|
511
422
|
raise_tool_error(QingflowApiError.config_error(message))
|
|
512
423
|
raise_tool_error(
|
|
513
424
|
QingflowApiError.config_error(
|
|
514
|
-
f"task action '{normalized_action}' is not currently available for
|
|
425
|
+
f"task action '{normalized_action}' is not currently available for app_key='{resolved_app_key}' record_id={record_id_text} workflow_node_id={resolved_workflow_node_id}"
|
|
515
426
|
)
|
|
516
427
|
)
|
|
517
428
|
feedback_required_for = capabilities.get("action_constraints", {}).get("feedback_required_for") or []
|
|
@@ -559,7 +470,7 @@ class TaskContextTools(ToolBase):
|
|
|
559
470
|
prepared_fields=prepared_fields,
|
|
560
471
|
)
|
|
561
472
|
except QingflowApiError as error:
|
|
562
|
-
if
|
|
473
|
+
if error.backend_code == 46001:
|
|
563
474
|
return self._task_action_visibility_unverified_response(
|
|
564
475
|
profile=profile,
|
|
565
476
|
session_profile=session_profile,
|
|
@@ -607,8 +518,6 @@ class TaskContextTools(ToolBase):
|
|
|
607
518
|
"ok": bool(raw.get("ok", True)) and status != "failed",
|
|
608
519
|
"status": status,
|
|
609
520
|
"error_code": error_code,
|
|
610
|
-
"action_executed": True,
|
|
611
|
-
"safe_to_retry": False,
|
|
612
521
|
"request_route": raw.get("request_route") or self._request_route_payload(context),
|
|
613
522
|
"warnings": warnings,
|
|
614
523
|
"verification": verification,
|
|
@@ -771,8 +680,6 @@ class TaskContextTools(ToolBase):
|
|
|
771
680
|
"http_status": error.http_status,
|
|
772
681
|
"backend_code": error.backend_code,
|
|
773
682
|
"category": error.category,
|
|
774
|
-
"request_id": error.request_id,
|
|
775
|
-
"message": error.message,
|
|
776
683
|
}
|
|
777
684
|
|
|
778
685
|
log_items: list[dict[str, Any]] = []
|
|
@@ -800,8 +707,6 @@ class TaskContextTools(ToolBase):
|
|
|
800
707
|
"http_status": error.http_status,
|
|
801
708
|
"backend_code": error.backend_code,
|
|
802
709
|
"category": error.category,
|
|
803
|
-
"request_id": error.request_id,
|
|
804
|
-
"message": error.message,
|
|
805
710
|
}
|
|
806
711
|
|
|
807
712
|
todo_items = self._safe_task_list_items(profile=profile, task_box="todo", app_key=app_key)
|
|
@@ -851,7 +756,7 @@ class TaskContextTools(ToolBase):
|
|
|
851
756
|
runtime_consumed_after_action = bool(
|
|
852
757
|
runtime_verified
|
|
853
758
|
and isinstance(record_state_error, dict)
|
|
854
|
-
and
|
|
759
|
+
and record_state_error.get("backend_code") == 46001
|
|
855
760
|
)
|
|
856
761
|
if runtime_consumed_after_action:
|
|
857
762
|
verification["record_state_scope"] = "current_node_runtime"
|
|
@@ -866,28 +771,6 @@ class TaskContextTools(ToolBase):
|
|
|
866
771
|
),
|
|
867
772
|
}
|
|
868
773
|
)
|
|
869
|
-
permission_blocked_sources: list[str] = []
|
|
870
|
-
if (
|
|
871
|
-
isinstance(verification.get("record_state_error"), dict)
|
|
872
|
-
and _is_permission_context_error_payload(verification["record_state_error"])
|
|
873
|
-
):
|
|
874
|
-
permission_blocked_sources.append("record_state")
|
|
875
|
-
if (
|
|
876
|
-
isinstance(verification.get("workflow_log_error"), dict)
|
|
877
|
-
and _is_permission_context_error_payload(verification["workflow_log_error"])
|
|
878
|
-
):
|
|
879
|
-
permission_blocked_sources.append("workflow_log")
|
|
880
|
-
if permission_blocked_sources:
|
|
881
|
-
warnings.append(
|
|
882
|
-
{
|
|
883
|
-
"code": "TASK_ACTION_VERIFICATION_PERMISSION_UNAVAILABLE",
|
|
884
|
-
"message": (
|
|
885
|
-
"task action executed, but some post-action verification reads are unavailable "
|
|
886
|
-
"in this permission context; do not treat the verification read failure as action denial."
|
|
887
|
-
),
|
|
888
|
-
"sources": permission_blocked_sources,
|
|
889
|
-
}
|
|
890
|
-
)
|
|
891
774
|
verification["runtime_continuation_verified"] = runtime_verified
|
|
892
775
|
if not runtime_verified:
|
|
893
776
|
warnings.append(
|
|
@@ -1048,9 +931,7 @@ class TaskContextTools(ToolBase):
|
|
|
1048
931
|
baseline["workflow_log_visible"] = True
|
|
1049
932
|
baseline["workflow_log_count"] = len(log_items)
|
|
1050
933
|
baseline["workflow_log_digest"] = self._workflow_log_digest(log_items)
|
|
1051
|
-
except QingflowApiError
|
|
1052
|
-
if is_auth_like_error(exc):
|
|
1053
|
-
raise
|
|
934
|
+
except QingflowApiError:
|
|
1054
935
|
pass
|
|
1055
936
|
todo_items = self._safe_task_list_items(profile=profile, task_box="todo", app_key=app_key)
|
|
1056
937
|
baseline["downstream_todo_nodes"] = sorted(
|
|
@@ -1177,9 +1058,7 @@ class TaskContextTools(ToolBase):
|
|
|
1177
1058
|
page=1,
|
|
1178
1059
|
page_size=50,
|
|
1179
1060
|
)
|
|
1180
|
-
except QingflowApiError
|
|
1181
|
-
if not _is_task_optional_read_error(exc):
|
|
1182
|
-
raise
|
|
1061
|
+
except QingflowApiError:
|
|
1183
1062
|
return []
|
|
1184
1063
|
items = response.get("items") if isinstance(response, dict) else None
|
|
1185
1064
|
if not isinstance(items, list):
|
|
@@ -1273,36 +1152,21 @@ class TaskContextTools(ToolBase):
|
|
|
1273
1152
|
task_id_text = normalize_positive_id_text(task_id, field_name="task_id")
|
|
1274
1153
|
searched_task_boxes = ("todo", "initiated", "cc", "done")
|
|
1275
1154
|
incomplete_task_boxes: list[str] = []
|
|
1276
|
-
inaccessible_task_boxes: list[dict[str, Any]] = []
|
|
1277
|
-
truncated_task_boxes: list[dict[str, Any]] = []
|
|
1278
1155
|
page_size = 100
|
|
1279
1156
|
for task_box in searched_task_boxes:
|
|
1280
1157
|
page = 1
|
|
1281
1158
|
page_amount: int | None = None
|
|
1282
1159
|
while True:
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
)
|
|
1294
|
-
except QingflowApiError as exc:
|
|
1295
|
-
if not _is_optional_task_box_locator_error(exc):
|
|
1296
|
-
raise
|
|
1297
|
-
inaccessible_task_boxes.append(
|
|
1298
|
-
{
|
|
1299
|
-
"task_box": task_box,
|
|
1300
|
-
"backend_code": exc.backend_code,
|
|
1301
|
-
"http_status": exc.http_status,
|
|
1302
|
-
"request_id": exc.request_id,
|
|
1303
|
-
}
|
|
1304
|
-
)
|
|
1305
|
-
break
|
|
1160
|
+
response = self._list_normalized_task_items(
|
|
1161
|
+
profile=profile,
|
|
1162
|
+
task_box=task_box,
|
|
1163
|
+
flow_status="all",
|
|
1164
|
+
app_key=None,
|
|
1165
|
+
workflow_node_id=None,
|
|
1166
|
+
query=None,
|
|
1167
|
+
page=page,
|
|
1168
|
+
page_size=page_size,
|
|
1169
|
+
)
|
|
1306
1170
|
items = response.get("items") if isinstance(response.get("items"), list) else []
|
|
1307
1171
|
for item in items:
|
|
1308
1172
|
if not isinstance(item, dict) or not ids_equal(item.get("task_id"), task_id_text):
|
|
@@ -1328,52 +1192,17 @@ class TaskContextTools(ToolBase):
|
|
|
1328
1192
|
break
|
|
1329
1193
|
if not items or len(items) < page_size:
|
|
1330
1194
|
break
|
|
1331
|
-
if page >= TASK_LOCATOR_MAX_SCAN_PAGES_PER_BOX:
|
|
1332
|
-
truncated_task_boxes.append(
|
|
1333
|
-
{
|
|
1334
|
-
"task_box": task_box,
|
|
1335
|
-
"max_pages": TASK_LOCATOR_MAX_SCAN_PAGES_PER_BOX,
|
|
1336
|
-
"page_size": page_size,
|
|
1337
|
-
"reported_page_amount": page_amount,
|
|
1338
|
-
}
|
|
1339
|
-
)
|
|
1340
|
-
break
|
|
1341
1195
|
page += 1
|
|
1342
1196
|
if incomplete_task_boxes:
|
|
1343
1197
|
searched = ", ".join(incomplete_task_boxes)
|
|
1344
1198
|
raise_tool_error(
|
|
1345
1199
|
QingflowApiError.config_error(
|
|
1346
|
-
f"task_id={task_id_text} resolved to an incomplete task locator in task_box={searched};
|
|
1347
|
-
)
|
|
1348
|
-
)
|
|
1349
|
-
if inaccessible_task_boxes:
|
|
1350
|
-
searched = ", ".join(str(item.get("task_box")) for item in inaccessible_task_boxes)
|
|
1351
|
-
raise_tool_error(
|
|
1352
|
-
QingflowApiError.config_error(
|
|
1353
|
-
f"task_id={task_id_text} was not found in visible task boxes; some task boxes were not searchable in the current permission context: {searched}. Rerun task_list and use data.items[].task_id; do not substitute a row number or rebuild the locator.",
|
|
1354
|
-
details={
|
|
1355
|
-
"task_id": task_id_text,
|
|
1356
|
-
"searched_task_boxes": list(searched_task_boxes),
|
|
1357
|
-
"inaccessible_task_boxes": inaccessible_task_boxes,
|
|
1358
|
-
},
|
|
1359
|
-
)
|
|
1360
|
-
)
|
|
1361
|
-
if truncated_task_boxes:
|
|
1362
|
-
raise_tool_error(
|
|
1363
|
-
QingflowApiError.config_error(
|
|
1364
|
-
f"task_id={task_id_text} was not found in the first {TASK_LOCATOR_MAX_SCAN_PAGES_PER_BOX} pages of each visible task box. Rerun task_list with a query or the relevant page and pass the exact data.items[].task_id; do not guess task ids.",
|
|
1365
|
-
details={
|
|
1366
|
-
"task_id": task_id_text,
|
|
1367
|
-
"searched_task_boxes": list(searched_task_boxes),
|
|
1368
|
-
"truncated_task_boxes": truncated_task_boxes,
|
|
1369
|
-
"max_pages_per_task_box": TASK_LOCATOR_MAX_SCAN_PAGES_PER_BOX,
|
|
1370
|
-
"page_size": page_size,
|
|
1371
|
-
},
|
|
1200
|
+
f"task_id={task_id_text} resolved to an incomplete task locator in task_box={searched}; please refresh the task list and retry"
|
|
1372
1201
|
)
|
|
1373
1202
|
)
|
|
1374
1203
|
raise_tool_error(
|
|
1375
1204
|
QingflowApiError.config_error(
|
|
1376
|
-
f"task_id={task_id_text} was not found in the current visible task boxes (todo, initiated, cc, done)
|
|
1205
|
+
f"task_id={task_id_text} was not found in the current visible task boxes (todo, initiated, cc, done)"
|
|
1377
1206
|
)
|
|
1378
1207
|
)
|
|
1379
1208
|
|
|
@@ -1395,7 +1224,6 @@ class TaskContextTools(ToolBase):
|
|
|
1395
1224
|
resolved_app_key = str(locator["app_key"])
|
|
1396
1225
|
resolved_record_id = normalize_positive_id_int(locator["record_id"], field_name="record_id")
|
|
1397
1226
|
resolved_workflow_node_id = int(locator["workflow_node_id"])
|
|
1398
|
-
resolved_task_box = str(locator.get("task_box") or "todo")
|
|
1399
1227
|
explicit_app_key = (app_key or "").strip()
|
|
1400
1228
|
if explicit_app_key and explicit_app_key != resolved_app_key:
|
|
1401
1229
|
raise_tool_error(
|
|
@@ -1420,10 +1248,8 @@ class TaskContextTools(ToolBase):
|
|
|
1420
1248
|
else:
|
|
1421
1249
|
resolved_record_id = normalize_positive_id_int(record_id, field_name="record_id")
|
|
1422
1250
|
resolved_workflow_node_id = int(workflow_node_id)
|
|
1423
|
-
resolved_task_box = "todo"
|
|
1424
1251
|
return {
|
|
1425
1252
|
"task_id": task_id_text,
|
|
1426
|
-
"task_box": resolved_task_box,
|
|
1427
1253
|
"app_key": resolved_app_key,
|
|
1428
1254
|
"record_id": resolved_record_id,
|
|
1429
1255
|
"record_id_text": stringify_backend_id(resolved_record_id),
|
|
@@ -1481,7 +1307,6 @@ class TaskContextTools(ToolBase):
|
|
|
1481
1307
|
resolved_record_id = int(locator["record_id"])
|
|
1482
1308
|
record_id_text = str(locator["record_id_text"] or "")
|
|
1483
1309
|
resolved_workflow_node_id = int(locator["workflow_node_id"])
|
|
1484
|
-
resolved_task_box = str(locator.get("task_box") or "todo")
|
|
1485
1310
|
self._require_app_record_and_node(resolved_app_key, resolved_record_id, resolved_workflow_node_id)
|
|
1486
1311
|
task_context = self._build_task_context(
|
|
1487
1312
|
profile=profile,
|
|
@@ -1489,7 +1314,6 @@ class TaskContextTools(ToolBase):
|
|
|
1489
1314
|
app_key=resolved_app_key,
|
|
1490
1315
|
record_id=resolved_record_id,
|
|
1491
1316
|
workflow_node_id=resolved_workflow_node_id,
|
|
1492
|
-
task_box=resolved_task_box,
|
|
1493
1317
|
include_candidates=False,
|
|
1494
1318
|
include_associated_reports=True,
|
|
1495
1319
|
current_uid=session_profile.uid,
|
|
@@ -1563,7 +1387,7 @@ class TaskContextTools(ToolBase):
|
|
|
1563
1387
|
|
|
1564
1388
|
chart_key = str(report_item.get("chart_key") or "")
|
|
1565
1389
|
source_type = report_item.get("source_type")
|
|
1566
|
-
if source_type
|
|
1390
|
+
if source_type == "qingbi":
|
|
1567
1391
|
qingbi_context = BackendRequestContext(
|
|
1568
1392
|
base_url=_qingbi_base_url(context.base_url),
|
|
1569
1393
|
token=context.token,
|
|
@@ -1572,44 +1396,20 @@ class TaskContextTools(ToolBase):
|
|
|
1572
1396
|
qf_version=context.qf_version,
|
|
1573
1397
|
qf_version_source=context.qf_version_source,
|
|
1574
1398
|
)
|
|
1575
|
-
|
|
1576
|
-
"
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
"
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
params=chart_params,
|
|
1590
|
-
json_body=chart_payload,
|
|
1591
|
-
)
|
|
1592
|
-
except QingflowApiError as error:
|
|
1593
|
-
if not _should_retry_asos_data(error):
|
|
1594
|
-
raise
|
|
1595
|
-
try:
|
|
1596
|
-
chart_result = self.backend.request(
|
|
1597
|
-
"POST",
|
|
1598
|
-
qingbi_context,
|
|
1599
|
-
f"/qingbi/charts/data/qflow/{chart_key}/asos",
|
|
1600
|
-
params=chart_params,
|
|
1601
|
-
json_body=chart_payload,
|
|
1602
|
-
)
|
|
1603
|
-
except QingflowApiError as fallback_error:
|
|
1604
|
-
if not _should_retry_asos_data(fallback_error):
|
|
1605
|
-
raise
|
|
1606
|
-
chart_result = self.backend.request(
|
|
1607
|
-
"POST",
|
|
1608
|
-
qingbi_context,
|
|
1609
|
-
f"/qingbi/charts/data/{chart_key}",
|
|
1610
|
-
params=chart_params,
|
|
1611
|
-
json_body=chart_payload,
|
|
1612
|
-
)
|
|
1399
|
+
chart_result = self.backend.request(
|
|
1400
|
+
"POST",
|
|
1401
|
+
qingbi_context,
|
|
1402
|
+
f"/qingbi/charts/data/{chart_key}",
|
|
1403
|
+
params={
|
|
1404
|
+
"qfUUID": uuid4().hex,
|
|
1405
|
+
"pageNum": page,
|
|
1406
|
+
"pageSize": page_size,
|
|
1407
|
+
},
|
|
1408
|
+
json_body={
|
|
1409
|
+
"asosChartId": report_id,
|
|
1410
|
+
"keyQueValues": association_query.get("keyQueValues") or [],
|
|
1411
|
+
},
|
|
1412
|
+
)
|
|
1613
1413
|
request_route = {
|
|
1614
1414
|
"base_url": qingbi_context.base_url,
|
|
1615
1415
|
"qf_version": qingbi_context.qf_version,
|
|
@@ -1709,7 +1509,6 @@ class TaskContextTools(ToolBase):
|
|
|
1709
1509
|
resolved_record_id = int(locator["record_id"])
|
|
1710
1510
|
record_id_text = str(locator["record_id_text"] or "")
|
|
1711
1511
|
resolved_workflow_node_id = int(locator["workflow_node_id"])
|
|
1712
|
-
resolved_task_box = str(locator.get("task_box") or "todo")
|
|
1713
1512
|
self._require_app_record_and_node(resolved_app_key, resolved_record_id, resolved_workflow_node_id)
|
|
1714
1513
|
task_context = self._build_task_context(
|
|
1715
1514
|
profile=profile,
|
|
@@ -1717,16 +1516,12 @@ class TaskContextTools(ToolBase):
|
|
|
1717
1516
|
app_key=resolved_app_key,
|
|
1718
1517
|
record_id=resolved_record_id,
|
|
1719
1518
|
workflow_node_id=resolved_workflow_node_id,
|
|
1720
|
-
task_box=resolved_task_box,
|
|
1721
1519
|
include_candidates=False,
|
|
1722
1520
|
include_associated_reports=False,
|
|
1723
1521
|
current_uid=session_profile.uid,
|
|
1724
1522
|
)
|
|
1725
1523
|
visibility = task_context.get("visibility") or {}
|
|
1726
|
-
|
|
1727
|
-
raw_node = node.get("raw") if isinstance(node.get("raw"), dict) else {}
|
|
1728
|
-
audit_visibility_explicit = "auditRecordVisible" in raw_node
|
|
1729
|
-
if audit_visibility_explicit and not visibility.get("audit_record_visible"):
|
|
1524
|
+
if not visibility.get("audit_record_visible"):
|
|
1730
1525
|
raise_tool_error(
|
|
1731
1526
|
QingflowApiError.config_error(
|
|
1732
1527
|
f"workflow logs are not visible for app_key='{resolved_app_key}' record_id={record_id_text} workflow_node_id={resolved_workflow_node_id}"
|
|
@@ -1740,7 +1535,7 @@ class TaskContextTools(ToolBase):
|
|
|
1740
1535
|
"key": resolved_app_key,
|
|
1741
1536
|
"rowRecordId": resolved_record_id,
|
|
1742
1537
|
"nodeId": resolved_workflow_node_id,
|
|
1743
|
-
"role":
|
|
1538
|
+
"role": 3,
|
|
1744
1539
|
"pageNum": 1,
|
|
1745
1540
|
"pageSize": 200,
|
|
1746
1541
|
},
|
|
@@ -1751,7 +1546,7 @@ class TaskContextTools(ToolBase):
|
|
|
1751
1546
|
"ws_id": session_profile.selected_ws_id,
|
|
1752
1547
|
"ok": True,
|
|
1753
1548
|
"request_route": self._request_route_payload(context),
|
|
1754
|
-
"warnings":
|
|
1549
|
+
"warnings": [],
|
|
1755
1550
|
"output_profile": "normal",
|
|
1756
1551
|
"data": {
|
|
1757
1552
|
"selection": {
|
|
@@ -1760,9 +1555,7 @@ class TaskContextTools(ToolBase):
|
|
|
1760
1555
|
"workflow_node_id": resolved_workflow_node_id,
|
|
1761
1556
|
},
|
|
1762
1557
|
"visibility": {
|
|
1763
|
-
"audit_record_visible": (
|
|
1764
|
-
visibility.get("audit_record_visible") if audit_visibility_explicit else None
|
|
1765
|
-
),
|
|
1558
|
+
"audit_record_visible": visibility.get("audit_record_visible"),
|
|
1766
1559
|
"qrobot_record_visible": visibility.get("qrobot_record_visible"),
|
|
1767
1560
|
},
|
|
1768
1561
|
"items": items,
|
|
@@ -1787,94 +1580,43 @@ class TaskContextTools(ToolBase):
|
|
|
1787
1580
|
include_candidates: bool,
|
|
1788
1581
|
include_associated_reports: bool,
|
|
1789
1582
|
current_uid: int | None = None,
|
|
1790
|
-
task_box: str = "todo",
|
|
1791
1583
|
) -> dict[str, Any]:
|
|
1792
1584
|
"""执行内部辅助逻辑。"""
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
if not _is_optional_task_audit_info_error(exc):
|
|
1807
|
-
raise
|
|
1808
|
-
detail = self.backend.request(
|
|
1809
|
-
"GET",
|
|
1810
|
-
context,
|
|
1811
|
-
f"/app/{app_key}/apply/{record_id}",
|
|
1812
|
-
params={"role": role, "listType": list_type, "auditNodeId": workflow_node_id},
|
|
1813
|
-
)
|
|
1814
|
-
node_info = self._fallback_task_node_from_detail(
|
|
1815
|
-
detail,
|
|
1816
|
-
workflow_node_id=workflow_node_id,
|
|
1817
|
-
source_error=exc,
|
|
1818
|
-
)
|
|
1819
|
-
context_warnings.append(
|
|
1820
|
-
{
|
|
1821
|
-
"code": "TASK_AUDIT_INFO_UNAVAILABLE",
|
|
1822
|
-
"message": "Task context used the current task detail because auditInfo was unavailable in this permission context.",
|
|
1823
|
-
"backend_code": exc.backend_code,
|
|
1824
|
-
"request_id": exc.request_id,
|
|
1825
|
-
"http_status": exc.http_status,
|
|
1826
|
-
}
|
|
1827
|
-
)
|
|
1828
|
-
if detail is None:
|
|
1829
|
-
detail = self.backend.request(
|
|
1830
|
-
"GET",
|
|
1831
|
-
context,
|
|
1832
|
-
f"/app/{app_key}/apply/{record_id}",
|
|
1833
|
-
params={"role": role, "listType": list_type, "auditNodeId": workflow_node_id},
|
|
1834
|
-
)
|
|
1585
|
+
audit_infos = self.backend.request(
|
|
1586
|
+
"GET",
|
|
1587
|
+
context,
|
|
1588
|
+
f"/app/{app_key}/apply/{record_id}/auditInfo",
|
|
1589
|
+
params={"type": 1},
|
|
1590
|
+
)
|
|
1591
|
+
node_info = self._select_task_node(audit_infos, workflow_node_id, app_key=app_key, record_id=record_id)
|
|
1592
|
+
detail = self.backend.request(
|
|
1593
|
+
"GET",
|
|
1594
|
+
context,
|
|
1595
|
+
f"/app/{app_key}/apply/{record_id}",
|
|
1596
|
+
params={"role": 3, "listType": 1, "auditNodeId": workflow_node_id},
|
|
1597
|
+
)
|
|
1835
1598
|
app_name = self._task_app_name(context=context, app_key=app_key, detail=detail, node_info=node_info)
|
|
1836
1599
|
associated_report_visible = self._resolve_associated_report_visible(node_info, detail)
|
|
1837
|
-
associated_reports = {"visible": associated_report_visible, "loaded": False, "count": 0, "items": []
|
|
1600
|
+
associated_reports = {"visible": associated_report_visible, "loaded": False, "count": 0, "items": []}
|
|
1838
1601
|
if include_associated_reports and associated_report_visible:
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
"warnings": [],
|
|
1857
|
-
}
|
|
1858
|
-
except QingflowApiError as exc:
|
|
1859
|
-
if not _is_task_optional_read_error(exc):
|
|
1860
|
-
raise
|
|
1861
|
-
associated_reports = {
|
|
1862
|
-
"visible": True,
|
|
1863
|
-
"loaded": False,
|
|
1864
|
-
"count": 0,
|
|
1865
|
-
"items": [],
|
|
1866
|
-
"warnings": [
|
|
1867
|
-
{
|
|
1868
|
-
"code": "TASK_ASSOCIATED_REPORTS_UNAVAILABLE",
|
|
1869
|
-
"message": "Associated reports are not readable in this permission context; task detail remains available.",
|
|
1870
|
-
"backend_code": exc.backend_code,
|
|
1871
|
-
"request_id": exc.request_id,
|
|
1872
|
-
"http_status": exc.http_status,
|
|
1873
|
-
}
|
|
1874
|
-
],
|
|
1875
|
-
}
|
|
1602
|
+
asos_chart_list = self.backend.request(
|
|
1603
|
+
"GET",
|
|
1604
|
+
context,
|
|
1605
|
+
f"/app/{app_key}/asosChart",
|
|
1606
|
+
params={"role": 3, "auditNodeId": workflow_node_id, "beingDraft": False},
|
|
1607
|
+
)
|
|
1608
|
+
associated_items = [
|
|
1609
|
+
self._normalize_associated_report(item)
|
|
1610
|
+
for item in (asos_chart_list.get("asosCharts") or [])
|
|
1611
|
+
if isinstance(item, dict)
|
|
1612
|
+
]
|
|
1613
|
+
associated_reports = {
|
|
1614
|
+
"visible": True,
|
|
1615
|
+
"loaded": True,
|
|
1616
|
+
"count": len(associated_items),
|
|
1617
|
+
"items": associated_items,
|
|
1618
|
+
}
|
|
1876
1619
|
rollback_items: list[dict[str, Any]] = []
|
|
1877
|
-
rollback_warnings: list[JSONObject] = []
|
|
1878
1620
|
transfer_items: list[dict[str, Any]] = []
|
|
1879
1621
|
transfer_warnings: list[JSONObject] = []
|
|
1880
1622
|
transfer_pagination: JSONObject = {
|
|
@@ -1886,26 +1628,13 @@ class TaskContextTools(ToolBase):
|
|
|
1886
1628
|
"truncated": False,
|
|
1887
1629
|
}
|
|
1888
1630
|
if include_candidates:
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
rollback_items = self._rollback_candidate_items(rollback_result)
|
|
1897
|
-
except QingflowApiError as exc:
|
|
1898
|
-
if not _is_task_optional_read_error(exc):
|
|
1899
|
-
raise
|
|
1900
|
-
rollback_warnings.append(
|
|
1901
|
-
{
|
|
1902
|
-
"code": "TASK_ROLLBACK_CANDIDATES_UNAVAILABLE",
|
|
1903
|
-
"message": "Rollback candidates are not readable in this permission context; task detail remains available.",
|
|
1904
|
-
"backend_code": exc.backend_code,
|
|
1905
|
-
"request_id": exc.request_id,
|
|
1906
|
-
"http_status": exc.http_status,
|
|
1907
|
-
}
|
|
1908
|
-
)
|
|
1631
|
+
rollback_result = self.backend.request(
|
|
1632
|
+
"GET",
|
|
1633
|
+
context,
|
|
1634
|
+
f"/app/{app_key}/apply/{record_id}/revertNode",
|
|
1635
|
+
params={"auditNodeId": workflow_node_id},
|
|
1636
|
+
)
|
|
1637
|
+
rollback_items = self._rollback_candidate_items(rollback_result)
|
|
1909
1638
|
transfer_items, transfer_warnings, transfer_pagination = self._transfer_candidate_items(
|
|
1910
1639
|
context,
|
|
1911
1640
|
app_key=app_key,
|
|
@@ -1928,7 +1657,6 @@ class TaskContextTools(ToolBase):
|
|
|
1928
1657
|
context,
|
|
1929
1658
|
app_key=app_key,
|
|
1930
1659
|
workflow_node_id=workflow_node_id,
|
|
1931
|
-
node_info=node_info,
|
|
1932
1660
|
)
|
|
1933
1661
|
capabilities = self._build_capabilities(
|
|
1934
1662
|
node_info,
|
|
@@ -1975,9 +1703,7 @@ class TaskContextTools(ToolBase):
|
|
|
1975
1703
|
"transfer_members": transfer_items,
|
|
1976
1704
|
"loaded": include_candidates,
|
|
1977
1705
|
"transfer_pagination": transfer_pagination,
|
|
1978
|
-
"
|
|
1979
|
-
"transfer_warnings": transfer_warnings,
|
|
1980
|
-
"warnings": [*rollback_warnings, *transfer_warnings],
|
|
1706
|
+
"warnings": transfer_warnings,
|
|
1981
1707
|
},
|
|
1982
1708
|
"workflow_log_summary": {
|
|
1983
1709
|
"visible": visibility["audit_record_visible"],
|
|
@@ -1986,7 +1712,6 @@ class TaskContextTools(ToolBase):
|
|
|
1986
1712
|
"qrobot_log_visible": visibility["qrobot_record_visible"],
|
|
1987
1713
|
},
|
|
1988
1714
|
"update_schema": update_schema,
|
|
1989
|
-
"warnings": context_warnings,
|
|
1990
1715
|
}
|
|
1991
1716
|
|
|
1992
1717
|
def _compact_task_get_context(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -2056,14 +1781,12 @@ class TaskContextTools(ToolBase):
|
|
|
2056
1781
|
"loaded": bool(associated_reports.get("loaded")),
|
|
2057
1782
|
"count": len(associated_items),
|
|
2058
1783
|
"items": associated_items,
|
|
2059
|
-
"warnings": associated_reports.get("warnings") or [],
|
|
2060
1784
|
},
|
|
2061
1785
|
"rollback_candidates": {
|
|
2062
1786
|
"available": "rollback" in available_actions,
|
|
2063
1787
|
"loaded": bool(candidates.get("loaded")),
|
|
2064
1788
|
"count": len(rollback_items),
|
|
2065
1789
|
"items": rollback_items,
|
|
2066
|
-
"warnings": candidates.get("rollback_warnings") or [],
|
|
2067
1790
|
},
|
|
2068
1791
|
"transfer_candidates": {
|
|
2069
1792
|
"available": "transfer" in available_actions,
|
|
@@ -2071,7 +1794,7 @@ class TaskContextTools(ToolBase):
|
|
|
2071
1794
|
"count": len(transfer_items),
|
|
2072
1795
|
"items": transfer_items,
|
|
2073
1796
|
"pagination": transfer_pagination,
|
|
2074
|
-
"warnings": candidates.get("
|
|
1797
|
+
"warnings": candidates.get("warnings") or [],
|
|
2075
1798
|
},
|
|
2076
1799
|
},
|
|
2077
1800
|
}
|
|
@@ -2106,8 +1829,6 @@ class TaskContextTools(ToolBase):
|
|
|
2106
1829
|
metadata["blockers"] = blockers
|
|
2107
1830
|
if warnings:
|
|
2108
1831
|
metadata["warnings"] = warnings
|
|
2109
|
-
if update_schema.get("editable_question_ids_source"):
|
|
2110
|
-
metadata["editable_question_ids_source"] = update_schema.get("editable_question_ids_source")
|
|
2111
1832
|
return metadata
|
|
2112
1833
|
|
|
2113
1834
|
def _compact_initiator(self, payload: Any) -> dict[str, Any] | None:
|
|
@@ -2156,9 +1877,7 @@ class TaskContextTools(ToolBase):
|
|
|
2156
1877
|
) -> str | None:
|
|
2157
1878
|
try:
|
|
2158
1879
|
base_info = self.backend.request("GET", context, f"/app/{app_key}/baseInfo")
|
|
2159
|
-
except QingflowApiError
|
|
2160
|
-
if not _is_task_optional_read_error(exc):
|
|
2161
|
-
raise
|
|
1880
|
+
except QingflowApiError:
|
|
2162
1881
|
return None
|
|
2163
1882
|
if not isinstance(base_info, dict):
|
|
2164
1883
|
return None
|
|
@@ -2176,9 +1895,7 @@ class TaskContextTools(ToolBase):
|
|
|
2176
1895
|
) -> str | None:
|
|
2177
1896
|
try:
|
|
2178
1897
|
visible_apps = self.backend.request("GET", context, "/tag/apps")
|
|
2179
|
-
except QingflowApiError
|
|
2180
|
-
if not _is_task_optional_read_error(exc):
|
|
2181
|
-
raise
|
|
1898
|
+
except QingflowApiError:
|
|
2182
1899
|
return None
|
|
2183
1900
|
return self._find_task_app_name_in_visible_apps(visible_apps, app_key=app_key)
|
|
2184
1901
|
|
|
@@ -2378,54 +2095,6 @@ class TaskContextTools(ToolBase):
|
|
|
2378
2095
|
)
|
|
2379
2096
|
)
|
|
2380
2097
|
|
|
2381
|
-
def _fallback_task_node_from_detail(
|
|
2382
|
-
self,
|
|
2383
|
-
detail: dict[str, Any],
|
|
2384
|
-
*,
|
|
2385
|
-
workflow_node_id: int,
|
|
2386
|
-
source_error: QingflowApiError,
|
|
2387
|
-
) -> dict[str, Any]:
|
|
2388
|
-
"""Build a conservative task node snapshot from the readable task detail."""
|
|
2389
|
-
node_info: dict[str, Any] = {
|
|
2390
|
-
"auditNodeId": workflow_node_id,
|
|
2391
|
-
"nodeId": workflow_node_id,
|
|
2392
|
-
"auditNodeName": (
|
|
2393
|
-
detail.get("auditNodeName")
|
|
2394
|
-
or detail.get("nodeName")
|
|
2395
|
-
or detail.get("workflowNodeName")
|
|
2396
|
-
or detail.get("currentNodeName")
|
|
2397
|
-
),
|
|
2398
|
-
"_auditInfoUnavailable": True,
|
|
2399
|
-
"_auditInfoError": {
|
|
2400
|
-
"http_status": source_error.http_status,
|
|
2401
|
-
"backend_code": source_error.backend_code,
|
|
2402
|
-
"category": source_error.category,
|
|
2403
|
-
},
|
|
2404
|
-
}
|
|
2405
|
-
for key in (
|
|
2406
|
-
"canTransfer",
|
|
2407
|
-
"canRevert",
|
|
2408
|
-
"canUrge",
|
|
2409
|
-
"rejectBtnStatus",
|
|
2410
|
-
"canRevoke",
|
|
2411
|
-
"beingEndWorkflow",
|
|
2412
|
-
"beingCanApplyAgain",
|
|
2413
|
-
"beingSubmitCheck",
|
|
2414
|
-
"beingSubmitPreview",
|
|
2415
|
-
"feedbackRequiredOperationType",
|
|
2416
|
-
"queAuthSetting",
|
|
2417
|
-
"auditRecordVisible",
|
|
2418
|
-
"qrobotRecordBeingVisible",
|
|
2419
|
-
"beingWorkflowNodeFutureListVisible",
|
|
2420
|
-
"commentStatus",
|
|
2421
|
-
"asosChartVisible",
|
|
2422
|
-
):
|
|
2423
|
-
if key in detail:
|
|
2424
|
-
node_info[key] = detail[key]
|
|
2425
|
-
if "viewAsosChartVisible" in detail and "asosChartVisible" not in node_info:
|
|
2426
|
-
node_info["asosChartVisible"] = detail.get("viewAsosChartVisible")
|
|
2427
|
-
return node_info
|
|
2428
|
-
|
|
2429
2098
|
def _build_capabilities(
|
|
2430
2099
|
self,
|
|
2431
2100
|
node_info: dict[str, Any],
|
|
@@ -2490,18 +2159,35 @@ class TaskContextTools(ToolBase):
|
|
|
2490
2159
|
try:
|
|
2491
2160
|
app_schema = self._record_tools._get_form_schema(profile, context, app_key, force_refresh=False)
|
|
2492
2161
|
except QingflowApiError as error:
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2162
|
+
public_schema: JSONObject = {
|
|
2163
|
+
"schema_scope": "task_update_ready",
|
|
2164
|
+
"writable_fields": [],
|
|
2165
|
+
"payload_template": {},
|
|
2166
|
+
"blockers": ["TASK_UPDATE_SCHEMA_UNAVAILABLE"],
|
|
2167
|
+
"warnings": [
|
|
2168
|
+
{
|
|
2169
|
+
"code": "TASK_UPDATE_SCHEMA_UNAVAILABLE",
|
|
2170
|
+
"message": "task detail could not load the form schema for the current app, so node-scoped update schema is unavailable.",
|
|
2171
|
+
}
|
|
2172
|
+
],
|
|
2173
|
+
"selection": {
|
|
2174
|
+
"app_key": app_key,
|
|
2175
|
+
"record_id": record_id_text,
|
|
2176
|
+
"workflow_node_id": workflow_node_id,
|
|
2177
|
+
},
|
|
2178
|
+
"transport_error": {
|
|
2179
|
+
"http_status": error.http_status,
|
|
2180
|
+
"backend_code": error.backend_code,
|
|
2181
|
+
"category": error.category,
|
|
2182
|
+
},
|
|
2183
|
+
}
|
|
2184
|
+
return {
|
|
2185
|
+
"public_schema": public_schema,
|
|
2186
|
+
"index": None,
|
|
2187
|
+
"editable_question_ids": [],
|
|
2188
|
+
"effective_editable_question_ids": [],
|
|
2189
|
+
"editable_question_ids_source": "schema_unavailable",
|
|
2190
|
+
}
|
|
2505
2191
|
|
|
2506
2192
|
question_relations = _collect_question_relations(app_schema)
|
|
2507
2193
|
linked_field_ids = _collect_linked_required_field_ids(question_relations)
|
|
@@ -2574,7 +2260,6 @@ class TaskContextTools(ToolBase):
|
|
|
2574
2260
|
"record_id": record_id_text,
|
|
2575
2261
|
"workflow_node_id": workflow_node_id,
|
|
2576
2262
|
},
|
|
2577
|
-
"editable_question_ids_source": source,
|
|
2578
2263
|
}
|
|
2579
2264
|
return {
|
|
2580
2265
|
"public_schema": public_schema,
|
|
@@ -2584,96 +2269,6 @@ class TaskContextTools(ToolBase):
|
|
|
2584
2269
|
"editable_question_ids_source": source,
|
|
2585
2270
|
}
|
|
2586
2271
|
|
|
2587
|
-
def _build_task_update_schema_from_runtime_answers(
|
|
2588
|
-
self,
|
|
2589
|
-
*,
|
|
2590
|
-
profile: str,
|
|
2591
|
-
context: BackendRequestContext,
|
|
2592
|
-
app_key: str,
|
|
2593
|
-
record_id: int,
|
|
2594
|
-
workflow_node_id: int,
|
|
2595
|
-
node_info: dict[str, Any],
|
|
2596
|
-
current_answers: Any,
|
|
2597
|
-
source_error: QingflowApiError,
|
|
2598
|
-
) -> dict[str, Any]:
|
|
2599
|
-
"""Build node-scoped edit schema from the task detail when app schema is unavailable."""
|
|
2600
|
-
record_id_text = stringify_backend_id(record_id)
|
|
2601
|
-
editable_question_ids, schema_warnings, source = self._resolve_task_editable_question_ids(
|
|
2602
|
-
context,
|
|
2603
|
-
app_key=app_key,
|
|
2604
|
-
workflow_node_id=workflow_node_id,
|
|
2605
|
-
node_info=node_info,
|
|
2606
|
-
)
|
|
2607
|
-
schema_warnings.insert(
|
|
2608
|
-
0,
|
|
2609
|
-
{
|
|
2610
|
-
"code": "TASK_UPDATE_SCHEMA_APP_SCHEMA_UNAVAILABLE",
|
|
2611
|
-
"message": "task update schema used current task answers because the app applicant schema was unavailable in this permission context.",
|
|
2612
|
-
"transport_error": {
|
|
2613
|
-
"http_status": source_error.http_status,
|
|
2614
|
-
"backend_code": source_error.backend_code,
|
|
2615
|
-
"category": source_error.category,
|
|
2616
|
-
},
|
|
2617
|
-
},
|
|
2618
|
-
)
|
|
2619
|
-
index = _build_answer_backed_field_index(
|
|
2620
|
-
current_answers,
|
|
2621
|
-
field_id_filter={str(que_id) for que_id in editable_question_ids if que_id > 0},
|
|
2622
|
-
)
|
|
2623
|
-
effective_editable_ids = set(editable_question_ids)
|
|
2624
|
-
writable_fields: list[JSONObject] = []
|
|
2625
|
-
for field in index.by_id.values():
|
|
2626
|
-
if field.que_type in LAYOUT_ONLY_QUE_TYPES or field.que_id not in effective_editable_ids:
|
|
2627
|
-
continue
|
|
2628
|
-
editable_field = _clone_form_field(field, readonly=False)
|
|
2629
|
-
write_hints = self._record_tools._schema_write_hints(editable_field)
|
|
2630
|
-
if not bool(write_hints.get("writable")):
|
|
2631
|
-
continue
|
|
2632
|
-
writable_field = self._record_tools._ready_schema_field_payload(
|
|
2633
|
-
profile,
|
|
2634
|
-
context,
|
|
2635
|
-
editable_field,
|
|
2636
|
-
ws_id=context.ws_id,
|
|
2637
|
-
required_override=False,
|
|
2638
|
-
linkage_payloads_by_field_id={},
|
|
2639
|
-
)
|
|
2640
|
-
writable_field.setdefault("field_id", editable_field.que_id)
|
|
2641
|
-
writable_fields.append(writable_field)
|
|
2642
|
-
|
|
2643
|
-
blockers: list[str] = []
|
|
2644
|
-
if not writable_fields:
|
|
2645
|
-
blockers.append("NO_TASK_EDITABLE_FIELDS")
|
|
2646
|
-
schema_warnings.append(
|
|
2647
|
-
{
|
|
2648
|
-
"code": "NO_TASK_EDITABLE_FIELDS",
|
|
2649
|
-
"message": "the current task node does not expose any writable fields in the current task detail.",
|
|
2650
|
-
}
|
|
2651
|
-
)
|
|
2652
|
-
public_schema: JSONObject = {
|
|
2653
|
-
"schema_scope": "task_update_ready",
|
|
2654
|
-
"writable_fields": writable_fields,
|
|
2655
|
-
"payload_template": {
|
|
2656
|
-
item["title"]: self._record_tools._ready_schema_template_value(item)
|
|
2657
|
-
for item in writable_fields
|
|
2658
|
-
if isinstance(item, dict) and item.get("title")
|
|
2659
|
-
},
|
|
2660
|
-
"blockers": blockers,
|
|
2661
|
-
"warnings": schema_warnings,
|
|
2662
|
-
"selection": {
|
|
2663
|
-
"app_key": app_key,
|
|
2664
|
-
"record_id": record_id_text,
|
|
2665
|
-
"workflow_node_id": workflow_node_id,
|
|
2666
|
-
},
|
|
2667
|
-
"editable_question_ids_source": f"{source}_runtime_answers",
|
|
2668
|
-
}
|
|
2669
|
-
return {
|
|
2670
|
-
"public_schema": public_schema,
|
|
2671
|
-
"index": index,
|
|
2672
|
-
"editable_question_ids": sorted(editable_question_ids),
|
|
2673
|
-
"effective_editable_question_ids": sorted(effective_editable_ids),
|
|
2674
|
-
"editable_question_ids_source": f"{source}_runtime_answers",
|
|
2675
|
-
}
|
|
2676
|
-
|
|
2677
2272
|
def _augment_task_editable_field_index(
|
|
2678
2273
|
self,
|
|
2679
2274
|
*,
|
|
@@ -2733,15 +2328,12 @@ class TaskContextTools(ToolBase):
|
|
|
2733
2328
|
if question_ids:
|
|
2734
2329
|
return question_ids, warnings, "workflow_editable_que_ids"
|
|
2735
2330
|
except QingflowApiError as error:
|
|
2736
|
-
if not
|
|
2331
|
+
if error.backend_code not in {40002, 40027, 404} and error.http_status != 404:
|
|
2737
2332
|
raise
|
|
2738
2333
|
warnings.append(
|
|
2739
2334
|
{
|
|
2740
2335
|
"code": "TASK_EDITABLE_IDS_FALLBACK",
|
|
2741
2336
|
"message": "editable question ids endpoint is unavailable in the current route; task update schema fell back to queAuthSetting and may be conservative.",
|
|
2742
|
-
"backend_code": error.backend_code,
|
|
2743
|
-
"http_status": error.http_status,
|
|
2744
|
-
"request_id": error.request_id,
|
|
2745
2337
|
}
|
|
2746
2338
|
)
|
|
2747
2339
|
fallback_ids = self._editable_ids_from_que_auth_setting(node_info.get("queAuthSetting"))
|
|
@@ -2753,7 +2345,6 @@ class TaskContextTools(ToolBase):
|
|
|
2753
2345
|
*,
|
|
2754
2346
|
app_key: str,
|
|
2755
2347
|
workflow_node_id: int,
|
|
2756
|
-
node_info: dict[str, Any],
|
|
2757
2348
|
) -> tuple[bool, list[JSONObject], str]:
|
|
2758
2349
|
"""执行内部辅助逻辑。"""
|
|
2759
2350
|
try:
|
|
@@ -2763,28 +2354,15 @@ class TaskContextTools(ToolBase):
|
|
|
2763
2354
|
f"/app/{app_key}/auditNode/{workflow_node_id}/editableQueIds",
|
|
2764
2355
|
)
|
|
2765
2356
|
except QingflowApiError as error:
|
|
2766
|
-
if not _is_task_optional_read_error(error):
|
|
2767
|
-
raise
|
|
2768
|
-
fallback_ids = self._editable_ids_from_que_auth_setting(node_info.get("queAuthSetting"))
|
|
2769
|
-
fallback_source = "que_auth_setting" if fallback_ids else "backend_editable_que_ids_unavailable"
|
|
2770
2357
|
warning: JSONObject = {
|
|
2771
|
-
"code": "
|
|
2772
|
-
"message":
|
|
2773
|
-
"save_only availability used current task queAuthSetting because backend editableQueIds was unavailable in this permission context."
|
|
2774
|
-
if fallback_ids
|
|
2775
|
-
else "save_only is hidden because backend editableQueIds is unavailable and current task detail does not expose editable fields."
|
|
2776
|
-
),
|
|
2358
|
+
"code": "TASK_SAVE_ONLY_SIGNAL_UNAVAILABLE",
|
|
2359
|
+
"message": "save_only is hidden because backend editableQueIds is unavailable for the current node; MCP no longer infers save_only from local schema reconstruction.",
|
|
2777
2360
|
}
|
|
2778
2361
|
if error.backend_code is not None:
|
|
2779
2362
|
warning["backend_code"] = error.backend_code
|
|
2780
2363
|
if error.http_status is not None:
|
|
2781
2364
|
warning["http_status"] = error.http_status
|
|
2782
|
-
|
|
2783
|
-
warning["request_id"] = error.request_id
|
|
2784
|
-
if fallback_ids:
|
|
2785
|
-
warning["field_ids"] = sorted(fallback_ids)
|
|
2786
|
-
return True, [warning], fallback_source
|
|
2787
|
-
return False, [warning], fallback_source
|
|
2365
|
+
return False, [warning], "backend_editable_que_ids_unavailable"
|
|
2788
2366
|
return bool(self._extract_question_ids(payload)), [], "workflow_editable_que_ids"
|
|
2789
2367
|
|
|
2790
2368
|
def _extract_question_ids(self, payload: Any) -> set[int]:
|
|
@@ -3077,33 +2655,12 @@ class TaskContextTools(ToolBase):
|
|
|
3077
2655
|
warnings: list[JSONObject] = []
|
|
3078
2656
|
|
|
3079
2657
|
while page_num <= max_pages:
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
)
|
|
3087
|
-
except QingflowApiError as exc:
|
|
3088
|
-
if not _is_task_optional_read_error(exc):
|
|
3089
|
-
raise
|
|
3090
|
-
warnings.append(
|
|
3091
|
-
{
|
|
3092
|
-
"code": "TASK_TRANSFER_CANDIDATES_UNAVAILABLE",
|
|
3093
|
-
"message": "Transfer candidates are not readable in this permission context; task detail remains available.",
|
|
3094
|
-
"backend_code": exc.backend_code,
|
|
3095
|
-
"request_id": exc.request_id,
|
|
3096
|
-
"http_status": exc.http_status,
|
|
3097
|
-
}
|
|
3098
|
-
)
|
|
3099
|
-
return items, warnings, {
|
|
3100
|
-
"loaded": False,
|
|
3101
|
-
"page_size": page_size,
|
|
3102
|
-
"fetched_pages": fetched_pages,
|
|
3103
|
-
"reported_total": reported_total,
|
|
3104
|
-
"page_amount": page_amount,
|
|
3105
|
-
"truncated": False,
|
|
3106
|
-
}
|
|
2658
|
+
result = self.backend.request(
|
|
2659
|
+
"GET",
|
|
2660
|
+
context,
|
|
2661
|
+
f"/app/{app_key}/apply/{record_id}/transfer/member",
|
|
2662
|
+
params={"pageNum": page_num, "pageSize": page_size, "auditNodeId": workflow_node_id},
|
|
2663
|
+
)
|
|
3107
2664
|
fetched_pages += 1
|
|
3108
2665
|
raw_items = _approval_page_items(result)
|
|
3109
2666
|
fetched_raw_count += len(raw_items)
|
|
@@ -3268,9 +2825,7 @@ class TaskContextTools(ToolBase):
|
|
|
3268
2825
|
context,
|
|
3269
2826
|
f"/chart/{chart_key}/auth",
|
|
3270
2827
|
)
|
|
3271
|
-
except QingflowApiError
|
|
3272
|
-
if not _is_task_optional_read_error(error):
|
|
3273
|
-
raise
|
|
2828
|
+
except QingflowApiError:
|
|
3274
2829
|
return False
|
|
3275
2830
|
if not isinstance(auth, dict):
|
|
3276
2831
|
return False
|
|
@@ -3447,62 +3002,3 @@ class TaskContextTools(ToolBase):
|
|
|
3447
3002
|
"qf_version": context.qf_version,
|
|
3448
3003
|
"qf_version_source": context.qf_version_source or ("context" if context.qf_version else "unknown"),
|
|
3449
3004
|
}
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
def _is_optional_task_audit_info_error(error: QingflowApiError) -> bool:
|
|
3453
|
-
"""Whether task auditInfo may be replaced by current task detail."""
|
|
3454
|
-
if is_auth_like_error(error):
|
|
3455
|
-
return False
|
|
3456
|
-
return _is_task_permission_context_error(error) or error.http_status == 404
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
def _task_box_record_list_type(task_box: str | None) -> int:
|
|
3460
|
-
normalized = str(task_box or "todo").strip().lower()
|
|
3461
|
-
if normalized == "initiated":
|
|
3462
|
-
return 14
|
|
3463
|
-
if normalized == "done":
|
|
3464
|
-
return 2
|
|
3465
|
-
if normalized == "cc":
|
|
3466
|
-
return 12
|
|
3467
|
-
return 1
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
def _task_box_record_role(task_box: str | None) -> int:
|
|
3471
|
-
normalized = str(task_box or "todo").strip().lower()
|
|
3472
|
-
if normalized == "initiated":
|
|
3473
|
-
return 2
|
|
3474
|
-
return 3
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
def _is_optional_task_box_locator_error(error: QingflowApiError) -> bool:
|
|
3478
|
-
"""Whether one task box may be skipped while resolving a task_id."""
|
|
3479
|
-
if is_auth_like_error(error):
|
|
3480
|
-
return False
|
|
3481
|
-
return _is_task_permission_context_error(error) or error.http_status == 404
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
def _is_task_optional_read_error(error: QingflowApiError) -> bool:
|
|
3485
|
-
if is_auth_like_error(error):
|
|
3486
|
-
return False
|
|
3487
|
-
backend_code = _task_backend_code(error)
|
|
3488
|
-
return backend_code in {40002, 40027, 404} or error.http_status == 404
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
def _is_task_permission_context_error(error: QingflowApiError) -> bool:
|
|
3492
|
-
if is_auth_like_error(error):
|
|
3493
|
-
return False
|
|
3494
|
-
return _task_backend_code(error) in {40002, 40027}
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
def _is_permission_context_error_payload(payload: dict[str, Any]) -> bool:
|
|
3498
|
-
if str(payload.get("category") or "").strip().lower() == "auth":
|
|
3499
|
-
return False
|
|
3500
|
-
if message_looks_like_invalid_token(payload.get("message")):
|
|
3501
|
-
return False
|
|
3502
|
-
if backend_code_value_int(payload.get("http_status")) == 401:
|
|
3503
|
-
return False
|
|
3504
|
-
return backend_code_value_int(payload.get("backend_code")) in {40002, 40027}
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
def _task_backend_code(error: QingflowApiError) -> int | None:
|
|
3508
|
-
return backend_code_int(error)
|