@qingflow-tech/qingflow-cli 1.1.48 → 1.1.49
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 +3 -3
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/qingflow_mcp/builder_facade/service.py +13 -0
- package/src/qingflow_mcp/cli/commands/task.py +10 -12
- package/src/qingflow_mcp/cli/commands/workspace.py +20 -7
- package/src/qingflow_mcp/cli/formatters.py +18 -17
- package/src/qingflow_mcp/cli/pagination.py +44 -0
- package/src/qingflow_mcp/response_trim.py +3 -0
- package/src/qingflow_mcp/tools/record_tools.py +74 -15
- package/src/qingflow_mcp/tools/task_context_tools.py +21 -4
- package/src/qingflow_mcp/tools/workspace_tools.py +9 -12
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Qingflow CLI 是轻流面向开发者和 AI Agent 的命令行工具。它把轻
|
|
|
7
7
|
## 安装
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install @qingflow-tech/qingflow-cli@1.1.
|
|
10
|
+
npm install @qingflow-tech/qingflow-cli@1.1.49
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
推荐从官网复制这一条一键安装 Qingflow CLI:
|
|
@@ -21,7 +21,7 @@ npx qingflow-cli@latest install
|
|
|
21
21
|
## 运行
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
npx -y -p @qingflow-tech/qingflow-cli@1.1.
|
|
24
|
+
npx -y -p @qingflow-tech/qingflow-cli@1.1.49 qingflow
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
全局安装后可以直接运行:
|
|
@@ -62,7 +62,7 @@ qingflow-skills install --agent codex --scope user
|
|
|
62
62
|
如果你通过一次性 `npx -p` 执行 skills 安装,建议加 `--copy`,避免 symlink 指向 npm 临时执行缓存:
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
|
-
npx -y -p @qingflow-tech/qingflow-cli@1.1.
|
|
65
|
+
npx -y -p @qingflow-tech/qingflow-cli@1.1.49 qingflow-skills install --agent codex --scope user --copy
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
`qingflow-skills install` 默认使用 symlink,且遇到同名 skill 不会覆盖;只有显式传入 `--force` 才会替换。
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -1933,6 +1933,12 @@ class AiBuilderFacade:
|
|
|
1933
1933
|
"error_code": None,
|
|
1934
1934
|
"recoverable": False,
|
|
1935
1935
|
"message": "resolved members",
|
|
1936
|
+
"pagination": {
|
|
1937
|
+
**(listed.get("data", {}).get("pagination", {})),
|
|
1938
|
+
"page": page_num,
|
|
1939
|
+
"page_size": page_size,
|
|
1940
|
+
"returned_items": len(items),
|
|
1941
|
+
},
|
|
1936
1942
|
"normalized_args": normalized_args,
|
|
1937
1943
|
"missing_fields": [],
|
|
1938
1944
|
"allowed_values": {},
|
|
@@ -1998,6 +2004,13 @@ class AiBuilderFacade:
|
|
|
1998
2004
|
"error_code": None,
|
|
1999
2005
|
"recoverable": False,
|
|
2000
2006
|
"message": "resolved roles",
|
|
2007
|
+
"pagination": {
|
|
2008
|
+
"page": page_num,
|
|
2009
|
+
"page_size": page_size,
|
|
2010
|
+
"returned_items": len(items),
|
|
2011
|
+
"reported_total": page.get("total"),
|
|
2012
|
+
"page_amount": page.get("pageAmount"),
|
|
2013
|
+
},
|
|
2001
2014
|
"normalized_args": normalized_args,
|
|
2002
2015
|
"missing_fields": [],
|
|
2003
2016
|
"allowed_values": {},
|
|
@@ -6,6 +6,7 @@ from typing import Any
|
|
|
6
6
|
|
|
7
7
|
from ..context import CliContext
|
|
8
8
|
from ..interaction import cancelled_result, resolve_interactive_selection, resolve_interactive_text_input
|
|
9
|
+
from ..pagination import collect_paginated_items
|
|
9
10
|
from ..terminal_ui import SelectionOption
|
|
10
11
|
from .common import load_object_arg, raise_config_error
|
|
11
12
|
|
|
@@ -264,18 +265,15 @@ def _choose_todo_task_interactively(
|
|
|
264
265
|
title: str = "选择待办",
|
|
265
266
|
):
|
|
266
267
|
def load_options() -> list[SelectionOption[str]]:
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
)
|
|
277
|
-
data = result.get("data") if isinstance(result, dict) and isinstance(result.get("data"), dict) else {}
|
|
278
|
-
items = data.get("items") if isinstance(data.get("items"), list) else []
|
|
268
|
+
def fetch_page(page, page_size):
|
|
269
|
+
result = context.task.task_list(
|
|
270
|
+
profile=args.profile, task_box="todo", flow_status="all",
|
|
271
|
+
app_key=None, workflow_node_id=None, query=None, page=page, page_size=page_size,
|
|
272
|
+
)
|
|
273
|
+
data = result.get("data") or {}
|
|
274
|
+
return data.get("items") or [], data.get("pagination") or {}
|
|
275
|
+
|
|
276
|
+
items = collect_paginated_items(fetch_page, id_key="task_id")
|
|
279
277
|
options: list[SelectionOption[str]] = []
|
|
280
278
|
for item in items:
|
|
281
279
|
if not isinstance(item, dict):
|
|
@@ -4,6 +4,7 @@ import argparse
|
|
|
4
4
|
|
|
5
5
|
from ..context import CliContext
|
|
6
6
|
from ..interaction import cancelled_result, resolve_interactive_selection
|
|
7
|
+
from ..pagination import collect_paginated_items
|
|
7
8
|
from ..terminal_ui import SelectionOption
|
|
8
9
|
from .common import raise_config_error
|
|
9
10
|
|
|
@@ -75,13 +76,25 @@ def _choose_workspace_interactively(args: argparse.Namespace, context: CliContex
|
|
|
75
76
|
session_profile = None
|
|
76
77
|
current_ws_id = getattr(session_profile, "selected_ws_id", None) if session_profile is not None else None
|
|
77
78
|
def load_options() -> list[SelectionOption[int]]:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
selected_workspace = None
|
|
80
|
+
|
|
81
|
+
def fetch_page(page_num, page_size):
|
|
82
|
+
nonlocal selected_workspace
|
|
83
|
+
result = context.workspace.workspace_list(
|
|
84
|
+
profile=args.profile, page_num=page_num, page_size=page_size, include_external=False,
|
|
85
|
+
)
|
|
86
|
+
if page_num == 1:
|
|
87
|
+
selected_workspace = result.get("selected_workspace")
|
|
88
|
+
page = result.get("page") or {}
|
|
89
|
+
return page.get("list") or [], {
|
|
90
|
+
"page_amount": page.get("pageAmount"),
|
|
91
|
+
"reported_total": page.get("total"),
|
|
92
|
+
"page_size": page.get("pageSize"),
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
items = collect_paginated_items(fetch_page, id_key="wsId")
|
|
96
|
+
if isinstance(selected_workspace, dict) and not any(item.get("wsId") == selected_workspace.get("wsId") for item in items):
|
|
97
|
+
items.append(selected_workspace)
|
|
85
98
|
options: list[SelectionOption[int]] = []
|
|
86
99
|
for item in items:
|
|
87
100
|
if not isinstance(item, dict):
|
|
@@ -147,7 +147,11 @@ def _format_workspace_list(result: dict[str, Any]) -> str:
|
|
|
147
147
|
str(item.get("remark") or ""),
|
|
148
148
|
]
|
|
149
149
|
)
|
|
150
|
-
|
|
150
|
+
text = _render_titled_table("Workspaces", ["ws_id", "name", "remark"], rows)
|
|
151
|
+
selected = result.get("selected_workspace")
|
|
152
|
+
if isinstance(selected, dict):
|
|
153
|
+
text += f"Selected Workspace: {selected.get('wsId')} / {selected.get('workspaceName') or '-'}\n"
|
|
154
|
+
return text
|
|
151
155
|
|
|
152
156
|
|
|
153
157
|
def _format_workspace_get(result: dict[str, Any]) -> str:
|
|
@@ -356,11 +360,9 @@ def _format_record_list(result: dict[str, Any]) -> str:
|
|
|
356
360
|
lines.append(f"- returned_count: {lookup.get('returned_count')}")
|
|
357
361
|
lines.append(f"- truncated: {lookup.get('truncated')}")
|
|
358
362
|
lines.append(f"Returned Records: {len(items)}")
|
|
359
|
-
for item in items
|
|
363
|
+
for item in items:
|
|
360
364
|
if isinstance(item, dict):
|
|
361
365
|
lines.append(json.dumps(item, ensure_ascii=False))
|
|
362
|
-
if len(items) > 10:
|
|
363
|
-
lines.append(f"... {len(items) - 10} more")
|
|
364
366
|
_append_warnings(lines, result.get("warnings"))
|
|
365
367
|
_append_verification(lines, result.get("verification"))
|
|
366
368
|
return "\n".join(lines) + "\n"
|
|
@@ -421,10 +423,11 @@ def _format_record_candidates(result: dict[str, Any]) -> str:
|
|
|
421
423
|
f"page_size={pagination.get('page_size')} / "
|
|
422
424
|
f"returned={pagination.get('returned_items')} / "
|
|
423
425
|
f"total={pagination.get('reported_total')} / "
|
|
424
|
-
f"pages={pagination.get('page_amount')}"
|
|
426
|
+
f"pages={pagination.get('page_amount')} / "
|
|
427
|
+
f"has_more={pagination.get('has_more')}"
|
|
425
428
|
)
|
|
426
429
|
lines.append(f"Candidates: {len(items)}")
|
|
427
|
-
for item in items
|
|
430
|
+
for item in items:
|
|
428
431
|
if not isinstance(item, dict):
|
|
429
432
|
lines.append(f"- {item}")
|
|
430
433
|
continue
|
|
@@ -446,8 +449,6 @@ def _format_record_candidates(result: dict[str, Any]) -> str:
|
|
|
446
449
|
if source_labels:
|
|
447
450
|
parts.append("sources=" + ",".join(source_labels))
|
|
448
451
|
lines.append("- " + " / ".join(parts))
|
|
449
|
-
if len(items) > 20:
|
|
450
|
-
lines.append(f"... {len(items) - 20} more")
|
|
451
452
|
_append_warnings(lines, result.get("warnings"))
|
|
452
453
|
_append_verification(lines, result.get("verification"))
|
|
453
454
|
return "\n".join(lines) + "\n"
|
|
@@ -944,11 +945,9 @@ def _format_task_associated_report_detail(result: dict[str, Any]) -> str:
|
|
|
944
945
|
result_payload = data.get("result") if isinstance(data.get("result"), dict) else {}
|
|
945
946
|
items = result_payload.get("items") if isinstance(result_payload.get("items"), list) else []
|
|
946
947
|
lines.append(f"Returned Records: {len(items)}")
|
|
947
|
-
for item in items
|
|
948
|
+
for item in items:
|
|
948
949
|
if isinstance(item, dict):
|
|
949
950
|
lines.append(json.dumps(item, ensure_ascii=False))
|
|
950
|
-
if len(items) > 10:
|
|
951
|
-
lines.append(f"... {len(items) - 10} more")
|
|
952
951
|
elif result_type == "chart_data":
|
|
953
952
|
result_payload = data.get("result") if isinstance(data.get("result"), dict) else {}
|
|
954
953
|
summary = result_payload.get("summary") if isinstance(result_payload.get("summary"), dict) else {}
|
|
@@ -956,11 +955,9 @@ def _format_task_associated_report_detail(result: dict[str, Any]) -> str:
|
|
|
956
955
|
if summary:
|
|
957
956
|
lines.append(f"Summary: {json.dumps(summary, ensure_ascii=False)}")
|
|
958
957
|
lines.append(f"Rows: {len(rows)}")
|
|
959
|
-
for row in rows
|
|
958
|
+
for row in rows:
|
|
960
959
|
if isinstance(row, dict):
|
|
961
960
|
lines.append(json.dumps(row, ensure_ascii=False))
|
|
962
|
-
if len(rows) > 10:
|
|
963
|
-
lines.append(f"... {len(rows) - 10} more")
|
|
964
961
|
resolved_filters = context.get("resolved_filters") if isinstance(context.get("resolved_filters"), list) else []
|
|
965
962
|
if resolved_filters:
|
|
966
963
|
lines.append(f"Resolved Filters: {len(resolved_filters)}")
|
|
@@ -1175,6 +1172,9 @@ def _format_export_direct(result: dict[str, Any]) -> str:
|
|
|
1175
1172
|
|
|
1176
1173
|
def _format_builder_summary(result: dict[str, Any]) -> str:
|
|
1177
1174
|
lines = []
|
|
1175
|
+
pagination = result.get("pagination")
|
|
1176
|
+
if isinstance(pagination, dict):
|
|
1177
|
+
lines.append("Pagination: " + json.dumps(pagination, ensure_ascii=False))
|
|
1178
1178
|
emitted: set[str] = set()
|
|
1179
1179
|
for key, label in (
|
|
1180
1180
|
("status", "Status"),
|
|
@@ -1222,10 +1222,11 @@ def _format_builder_summary(result: dict[str, Any]) -> str:
|
|
|
1222
1222
|
value = data.get(key)
|
|
1223
1223
|
if isinstance(value, list):
|
|
1224
1224
|
lines.append(f"{key}: {len(value)}")
|
|
1225
|
-
|
|
1225
|
+
visible_items = value if isinstance(pagination, dict) else value[:8]
|
|
1226
|
+
for item in visible_items:
|
|
1226
1227
|
lines.append("- " + _builder_item_summary(item))
|
|
1227
|
-
if len(value) >
|
|
1228
|
-
lines.append(f"... {len(value) -
|
|
1228
|
+
if len(value) > len(visible_items):
|
|
1229
|
+
lines.append(f"... {len(value) - len(visible_items)} more")
|
|
1229
1230
|
components = result.get("components") if isinstance(result.get("components"), list) else []
|
|
1230
1231
|
if components:
|
|
1231
1232
|
lines.append("Component Refs:")
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable
|
|
4
|
+
|
|
5
|
+
from .commands.common import raise_config_error
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def collect_paginated_items(
|
|
9
|
+
fetch_page: Callable[[int, int], tuple[list[dict[str, Any]], dict[str, Any]]],
|
|
10
|
+
*,
|
|
11
|
+
id_key: str,
|
|
12
|
+
page_size: int = 100,
|
|
13
|
+
) -> list[dict[str, Any]]:
|
|
14
|
+
items: list[dict[str, Any]] = []
|
|
15
|
+
seen: set[str] = set()
|
|
16
|
+
page = 1
|
|
17
|
+
fetched_count = 0
|
|
18
|
+
while True:
|
|
19
|
+
rows, pagination = fetch_page(page, page_size)
|
|
20
|
+
previous_count = len(items)
|
|
21
|
+
for row in rows:
|
|
22
|
+
if not isinstance(row, dict):
|
|
23
|
+
continue
|
|
24
|
+
key = str(row.get(id_key) or "")
|
|
25
|
+
if key and key not in seen:
|
|
26
|
+
seen.add(key)
|
|
27
|
+
items.append(row)
|
|
28
|
+
fetched_count += len(rows)
|
|
29
|
+
page_amount = pagination.get("page_amount")
|
|
30
|
+
total = pagination.get("reported_total")
|
|
31
|
+
if not rows:
|
|
32
|
+
break
|
|
33
|
+
if len(items) == previous_count:
|
|
34
|
+
raise_config_error("Pagination did not advance; the backend returned the same items again.")
|
|
35
|
+
if page_amount is not None:
|
|
36
|
+
if page >= int(page_amount):
|
|
37
|
+
break
|
|
38
|
+
elif total is not None:
|
|
39
|
+
if fetched_count >= int(total):
|
|
40
|
+
break
|
|
41
|
+
elif len(rows) < int(pagination.get("page_size") or page_size):
|
|
42
|
+
break
|
|
43
|
+
page += 1
|
|
44
|
+
return items
|
|
@@ -313,6 +313,9 @@ def _trim_workspace_list(payload: JSONObject) -> None:
|
|
|
313
313
|
page = payload.get("page")
|
|
314
314
|
if isinstance(page, dict):
|
|
315
315
|
_trim_item_list(page, "list", allowed=("wsId", "workspaceName", "remark"))
|
|
316
|
+
selected = payload.get("selected_workspace")
|
|
317
|
+
if isinstance(selected, dict):
|
|
318
|
+
payload["selected_workspace"] = _pick(selected, allowed=("wsId", "workspaceName", "remark"))
|
|
316
319
|
|
|
317
320
|
|
|
318
321
|
def _trim_workspace_get(payload: JSONObject) -> None:
|
|
@@ -442,7 +442,7 @@ class RecordTools(ToolBase):
|
|
|
442
442
|
description=(
|
|
443
443
|
"Browse Qingflow records with a schema-first list DSL. "
|
|
444
444
|
"Use record_browse_schema_get first, then pass field_id-only columns, where, and order_by clauses. "
|
|
445
|
-
"This route returns
|
|
445
|
+
"This route returns one page (10 rows by default) plus total counts for browse, sample inspection, and fuzzy record lookup; it is not for analysis."
|
|
446
446
|
)
|
|
447
447
|
)
|
|
448
448
|
def record_list(
|
|
@@ -1525,6 +1525,7 @@ class RecordTools(ToolBase):
|
|
|
1525
1525
|
)
|
|
1526
1526
|
warnings: list[JSONObject] = []
|
|
1527
1527
|
scope_source = "static_applicant_scope"
|
|
1528
|
+
backend_pagination: JSONObject = {}
|
|
1528
1529
|
try:
|
|
1529
1530
|
if runtime_lookup:
|
|
1530
1531
|
state = self._build_candidate_lookup_state(
|
|
@@ -1535,7 +1536,10 @@ class RecordTools(ToolBase):
|
|
|
1535
1536
|
workflow_node_id=workflow_node_id,
|
|
1536
1537
|
fields=normalized_fields,
|
|
1537
1538
|
)
|
|
1538
|
-
items = self._resolve_member_candidates_backend(
|
|
1539
|
+
items = self._resolve_member_candidates_backend(
|
|
1540
|
+
context, field, keyword=keyword, state=state,
|
|
1541
|
+
page_num=page_num, page_size=page_size, pagination=backend_pagination,
|
|
1542
|
+
)
|
|
1539
1543
|
scope_source = "backend_runtime_scope"
|
|
1540
1544
|
else:
|
|
1541
1545
|
items: list[JSONObject] | None = None
|
|
@@ -1548,7 +1552,10 @@ class RecordTools(ToolBase):
|
|
|
1548
1552
|
workflow_node_id=None,
|
|
1549
1553
|
fields={},
|
|
1550
1554
|
)
|
|
1551
|
-
items = self._resolve_member_candidates_backend(
|
|
1555
|
+
items = self._resolve_member_candidates_backend(
|
|
1556
|
+
context, field, keyword=keyword, state=state,
|
|
1557
|
+
page_num=page_num, page_size=page_size, pagination=backend_pagination,
|
|
1558
|
+
)
|
|
1552
1559
|
scope_source = "backend_applicant_scope"
|
|
1553
1560
|
if items is None:
|
|
1554
1561
|
items = self._resolve_member_candidates(context, field, keyword=keyword)
|
|
@@ -1581,7 +1588,7 @@ class RecordTools(ToolBase):
|
|
|
1581
1588
|
total = len(items)
|
|
1582
1589
|
start = (page_num - 1) * page_size
|
|
1583
1590
|
end = start + page_size
|
|
1584
|
-
page_items = items[start:end]
|
|
1591
|
+
page_items = items if backend_pagination else items[start:end]
|
|
1585
1592
|
page_amount = (total + page_size - 1) // page_size if total else 0
|
|
1586
1593
|
return {
|
|
1587
1594
|
"profile": profile,
|
|
@@ -1598,6 +1605,7 @@ class RecordTools(ToolBase):
|
|
|
1598
1605
|
"returned_items": len(page_items),
|
|
1599
1606
|
"reported_total": total,
|
|
1600
1607
|
"page_amount": page_amount,
|
|
1608
|
+
**backend_pagination,
|
|
1601
1609
|
},
|
|
1602
1610
|
"selection": {
|
|
1603
1611
|
"app_key": app_key,
|
|
@@ -1668,6 +1676,7 @@ class RecordTools(ToolBase):
|
|
|
1668
1676
|
)
|
|
1669
1677
|
warnings: list[JSONObject] = []
|
|
1670
1678
|
scope_source = "static_applicant_scope"
|
|
1679
|
+
backend_pagination: JSONObject = {}
|
|
1671
1680
|
try:
|
|
1672
1681
|
if runtime_lookup:
|
|
1673
1682
|
state = self._build_candidate_lookup_state(
|
|
@@ -1678,7 +1687,10 @@ class RecordTools(ToolBase):
|
|
|
1678
1687
|
workflow_node_id=workflow_node_id,
|
|
1679
1688
|
fields=normalized_fields,
|
|
1680
1689
|
)
|
|
1681
|
-
items = self._resolve_department_candidates_backend(
|
|
1690
|
+
items = self._resolve_department_candidates_backend(
|
|
1691
|
+
context, field, keyword=keyword, state=state,
|
|
1692
|
+
page_num=page_num, page_size=page_size, pagination=backend_pagination,
|
|
1693
|
+
)
|
|
1682
1694
|
scope_source = "backend_runtime_scope"
|
|
1683
1695
|
else:
|
|
1684
1696
|
items: list[JSONObject] | None = None
|
|
@@ -1691,7 +1703,10 @@ class RecordTools(ToolBase):
|
|
|
1691
1703
|
workflow_node_id=None,
|
|
1692
1704
|
fields={},
|
|
1693
1705
|
)
|
|
1694
|
-
items = self._resolve_department_candidates_backend(
|
|
1706
|
+
items = self._resolve_department_candidates_backend(
|
|
1707
|
+
context, field, keyword=keyword, state=state,
|
|
1708
|
+
page_num=page_num, page_size=page_size, pagination=backend_pagination,
|
|
1709
|
+
)
|
|
1695
1710
|
scope_source = "backend_applicant_scope"
|
|
1696
1711
|
if items is None:
|
|
1697
1712
|
items = self._resolve_department_candidates(context, field, keyword=keyword)
|
|
@@ -1710,7 +1725,10 @@ class RecordTools(ToolBase):
|
|
|
1710
1725
|
workflow_node_id=None,
|
|
1711
1726
|
fields={},
|
|
1712
1727
|
)
|
|
1713
|
-
items = self._resolve_department_candidates_backend(
|
|
1728
|
+
items = self._resolve_department_candidates_backend(
|
|
1729
|
+
context, field, keyword=keyword, state=state,
|
|
1730
|
+
page_num=page_num, page_size=page_size, pagination=backend_pagination,
|
|
1731
|
+
)
|
|
1714
1732
|
scope_source = "backend_applicant_scope"
|
|
1715
1733
|
warnings.append(
|
|
1716
1734
|
{
|
|
@@ -1741,7 +1759,7 @@ class RecordTools(ToolBase):
|
|
|
1741
1759
|
total = len(items)
|
|
1742
1760
|
start = (page_num - 1) * page_size
|
|
1743
1761
|
end = start + page_size
|
|
1744
|
-
page_items = items[start:end]
|
|
1762
|
+
page_items = items if backend_pagination else items[start:end]
|
|
1745
1763
|
page_amount = (total + page_size - 1) // page_size if total else 0
|
|
1746
1764
|
return {
|
|
1747
1765
|
"profile": profile,
|
|
@@ -1758,6 +1776,7 @@ class RecordTools(ToolBase):
|
|
|
1758
1776
|
"returned_items": len(page_items),
|
|
1759
1777
|
"reported_total": total,
|
|
1760
1778
|
"page_amount": page_amount,
|
|
1779
|
+
**backend_pagination,
|
|
1761
1780
|
},
|
|
1762
1781
|
"selection": {
|
|
1763
1782
|
"app_key": app_key,
|
|
@@ -1888,7 +1907,7 @@ class RecordTools(ToolBase):
|
|
|
1888
1907
|
query_fields: list[JSONObject | int] | None = None,
|
|
1889
1908
|
where: list[JSONObject],
|
|
1890
1909
|
order_by: list[JSONObject],
|
|
1891
|
-
limit: int =
|
|
1910
|
+
limit: int | None = None,
|
|
1892
1911
|
page: int = 1,
|
|
1893
1912
|
page_size: int = DEFAULT_RECORD_LIST_RETURN_LIMIT,
|
|
1894
1913
|
view_id: str | None = None,
|
|
@@ -1905,12 +1924,14 @@ class RecordTools(ToolBase):
|
|
|
1905
1924
|
normalized_columns = _normalize_public_column_selectors(columns)
|
|
1906
1925
|
normalized_query = _normalize_optional_text(query)
|
|
1907
1926
|
normalized_query_field_selectors = _normalize_public_query_field_selectors(query_fields or [])
|
|
1908
|
-
if limit <= 0:
|
|
1927
|
+
if limit is not None and limit <= 0:
|
|
1909
1928
|
raise_tool_error(QingflowApiError.config_error("limit must be positive"))
|
|
1910
1929
|
if page <= 0:
|
|
1911
1930
|
raise_tool_error(QingflowApiError.config_error("page must be positive"))
|
|
1912
1931
|
if page_size <= 0:
|
|
1913
1932
|
raise_tool_error(QingflowApiError.config_error("page_size must be positive"))
|
|
1933
|
+
if limit is not None:
|
|
1934
|
+
page_size = min(limit, page_size)
|
|
1914
1935
|
if not (
|
|
1915
1936
|
_normalize_optional_text(view_id)
|
|
1916
1937
|
or list_type is not None
|
|
@@ -1989,7 +2010,7 @@ class RecordTools(ToolBase):
|
|
|
1989
2010
|
search_que_ids=resolved_query_fields or None,
|
|
1990
2011
|
match_rules=match_rules,
|
|
1991
2012
|
sort_rules=sort_rules,
|
|
1992
|
-
max_rows=
|
|
2013
|
+
max_rows=page_size,
|
|
1993
2014
|
selected_fields=selected_fields,
|
|
1994
2015
|
output_profile="verbose" if normalized_output_profile in {"verbose", "normalized"} else DEFAULT_OUTPUT_PROFILE,
|
|
1995
2016
|
)
|
|
@@ -13314,6 +13335,9 @@ class RecordTools(ToolBase):
|
|
|
13314
13335
|
*,
|
|
13315
13336
|
keyword: str,
|
|
13316
13337
|
state: LookupResolutionState,
|
|
13338
|
+
page_num: int = 1,
|
|
13339
|
+
page_size: int = LOOKUP_RELATION_FILTER_PAGE_SIZE,
|
|
13340
|
+
pagination: JSONObject | None = None,
|
|
13317
13341
|
) -> list[JSONObject]:
|
|
13318
13342
|
"""执行内部辅助逻辑。"""
|
|
13319
13343
|
scope = field.member_select_scope if isinstance(field.member_select_scope, dict) else {}
|
|
@@ -13321,7 +13345,9 @@ class RecordTools(ToolBase):
|
|
|
13321
13345
|
isinstance(scope.get("externalMemberList"), list) and scope.get("externalMemberList")
|
|
13322
13346
|
or isinstance(scope.get("externalDepartList"), list) and scope.get("externalDepartList")
|
|
13323
13347
|
)
|
|
13324
|
-
payload = self._build_lookup_scope_payload(
|
|
13348
|
+
payload = self._build_lookup_scope_payload(
|
|
13349
|
+
state, keyword=keyword, que_id=field.que_id, page_num=page_num, page_size=page_size,
|
|
13350
|
+
)
|
|
13325
13351
|
route = f"/data/que/{field.que_id}/external/memberSelectScope" if use_external else f"/data/que/{field.que_id}/memberSelectScope"
|
|
13326
13352
|
try:
|
|
13327
13353
|
result = self.backend.request("POST", context, route, json_body=payload)
|
|
@@ -13334,7 +13360,10 @@ class RecordTools(ToolBase):
|
|
|
13334
13360
|
details={"field": _field_ref_payload(field), "received_value": keyword, "candidate_error": exc.to_dict()},
|
|
13335
13361
|
)
|
|
13336
13362
|
self._raise_candidate_lookup_failed(kind="member", field=field, value=keyword, error=exc)
|
|
13337
|
-
|
|
13363
|
+
items = self._normalize_backend_member_candidates(result, external=use_external)
|
|
13364
|
+
if pagination is not None:
|
|
13365
|
+
pagination.update(_candidate_page_metadata(result, page_num=page_num, page_size=page_size, returned_items=len(items)))
|
|
13366
|
+
return items
|
|
13338
13367
|
|
|
13339
13368
|
def _member_confirmation_candidates(self, candidates: list[JSONObject]) -> list[JSONObject]:
|
|
13340
13369
|
"""执行内部辅助逻辑。"""
|
|
@@ -13603,16 +13632,24 @@ class RecordTools(ToolBase):
|
|
|
13603
13632
|
*,
|
|
13604
13633
|
keyword: str,
|
|
13605
13634
|
state: LookupResolutionState,
|
|
13635
|
+
page_num: int = 1,
|
|
13636
|
+
page_size: int = LOOKUP_RELATION_FILTER_PAGE_SIZE,
|
|
13637
|
+
pagination: JSONObject | None = None,
|
|
13606
13638
|
) -> list[JSONObject]:
|
|
13607
13639
|
"""执行内部辅助逻辑。"""
|
|
13608
13640
|
scope = field.dept_select_scope if isinstance(field.dept_select_scope, dict) else {}
|
|
13609
13641
|
external = bool(
|
|
13610
13642
|
isinstance(scope.get("externalDepartList"), list) and scope.get("externalDepartList")
|
|
13611
13643
|
)
|
|
13612
|
-
payload = self._build_lookup_scope_payload(
|
|
13644
|
+
payload = self._build_lookup_scope_payload(
|
|
13645
|
+
state, keyword=keyword, que_id=field.que_id, page_num=page_num, page_size=page_size,
|
|
13646
|
+
)
|
|
13613
13647
|
payload["deptScope"] = "EXTERNAL" if external else "INTERNAL"
|
|
13614
13648
|
result = self.backend.request("POST", context, "/data/deptSelectScope/search", json_body=payload)
|
|
13615
|
-
|
|
13649
|
+
items = self._normalize_backend_department_candidates(result, external=external)
|
|
13650
|
+
if pagination is not None:
|
|
13651
|
+
pagination.update(_candidate_page_metadata(result, page_num=page_num, page_size=page_size, returned_items=len(items)))
|
|
13652
|
+
return items
|
|
13616
13653
|
|
|
13617
13654
|
def _department_confirmation_candidates(self, candidates: list[JSONObject]) -> list[JSONObject]:
|
|
13618
13655
|
"""执行内部辅助逻辑。"""
|
|
@@ -17881,6 +17918,28 @@ def _normalize_public_record_rows(rows: list[JSONValue]) -> list[JSONObject]:
|
|
|
17881
17918
|
return normalized_rows
|
|
17882
17919
|
|
|
17883
17920
|
|
|
17921
|
+
def _candidate_page_metadata(
|
|
17922
|
+
payload: JSONValue, *, page_num: int, page_size: int, returned_items: int,
|
|
17923
|
+
) -> JSONObject:
|
|
17924
|
+
page = payload if isinstance(payload, dict) else {}
|
|
17925
|
+
total = _coerce_count(page.get("total"))
|
|
17926
|
+
page_amount = _coerce_count(page.get("pageAmount"))
|
|
17927
|
+
# Some member-scope endpoints return rows without a total. A full page is not a total.
|
|
17928
|
+
if total is None and returned_items < page_size and (page_num == 1 or returned_items > 0):
|
|
17929
|
+
total = (page_num - 1) * page_size + returned_items
|
|
17930
|
+
if page_amount is None and total is not None:
|
|
17931
|
+
page_amount = (total + page_size - 1) // page_size
|
|
17932
|
+
has_more = page_num < page_amount if page_amount is not None else returned_items >= page_size
|
|
17933
|
+
return {
|
|
17934
|
+
"page": page_num,
|
|
17935
|
+
"page_size": page_size,
|
|
17936
|
+
"returned_items": returned_items,
|
|
17937
|
+
"reported_total": total,
|
|
17938
|
+
"page_amount": page_amount,
|
|
17939
|
+
"has_more": has_more,
|
|
17940
|
+
}
|
|
17941
|
+
|
|
17942
|
+
|
|
17884
17943
|
def _build_record_list_lookup_payload(
|
|
17885
17944
|
*,
|
|
17886
17945
|
query: str | None,
|
|
@@ -201,7 +201,15 @@ class TaskContextTools(ToolBase):
|
|
|
201
201
|
items = response["items"] if isinstance(response.get("items"), list) else []
|
|
202
202
|
page_amount = response.get("page_amount")
|
|
203
203
|
reported_total = response.get("reported_total")
|
|
204
|
-
|
|
204
|
+
search_has_no_matches = not items and not (_coerce_count(reported_total) or 0)
|
|
205
|
+
if query and search_has_no_matches and page > 1:
|
|
206
|
+
first_page = self._list_normalized_task_items(
|
|
207
|
+
profile=profile, task_box=task_box, flow_status=flow_status,
|
|
208
|
+
app_key=app_key, workflow_node_id=workflow_node_id,
|
|
209
|
+
query=query, page=1, page_size=page_size,
|
|
210
|
+
)
|
|
211
|
+
search_has_no_matches = not first_page["items"] and not (_coerce_count(first_page.get("reported_total")) or 0)
|
|
212
|
+
if query and search_has_no_matches:
|
|
205
213
|
fallback = self._task_list_local_query_fallback(
|
|
206
214
|
profile=profile,
|
|
207
215
|
task_box=task_box,
|
|
@@ -1238,6 +1246,7 @@ class TaskContextTools(ToolBase):
|
|
|
1238
1246
|
scan_page = 1
|
|
1239
1247
|
page_amount: int | None = None
|
|
1240
1248
|
matched_items: list[dict[str, Any]] = []
|
|
1249
|
+
scanned_items = 0
|
|
1241
1250
|
while True:
|
|
1242
1251
|
response = self._list_normalized_task_items(
|
|
1243
1252
|
profile=profile,
|
|
@@ -1250,14 +1259,22 @@ class TaskContextTools(ToolBase):
|
|
|
1250
1259
|
page_size=scan_page_size,
|
|
1251
1260
|
)
|
|
1252
1261
|
normalized_items = response.get("items") if isinstance(response.get("items"), list) else []
|
|
1262
|
+
scanned_items += len(normalized_items)
|
|
1253
1263
|
matched_items.extend(item for item in normalized_items if self._task_item_matches_query(item, query))
|
|
1254
1264
|
if page_amount is None:
|
|
1255
1265
|
coerced_page_amount = _coerce_count(response.get("page_amount"))
|
|
1256
1266
|
if coerced_page_amount is not None and coerced_page_amount > 0:
|
|
1257
1267
|
page_amount = coerced_page_amount
|
|
1258
|
-
if
|
|
1268
|
+
if not normalized_items:
|
|
1259
1269
|
break
|
|
1260
|
-
|
|
1270
|
+
reported_total = _coerce_count(response.get("reported_total"))
|
|
1271
|
+
if page_amount is not None:
|
|
1272
|
+
if scan_page >= page_amount:
|
|
1273
|
+
break
|
|
1274
|
+
elif reported_total is not None:
|
|
1275
|
+
if scanned_items >= reported_total:
|
|
1276
|
+
break
|
|
1277
|
+
elif len(normalized_items) < scan_page_size:
|
|
1261
1278
|
break
|
|
1262
1279
|
scan_page += 1
|
|
1263
1280
|
if not matched_items:
|
|
@@ -1527,7 +1544,7 @@ class TaskContextTools(ToolBase):
|
|
|
1527
1544
|
if report_item.get("graph_type") == "view":
|
|
1528
1545
|
viewgraph_key = str(report_item.get("chart_key") or "")
|
|
1529
1546
|
body = {
|
|
1530
|
-
"filter": {},
|
|
1547
|
+
"filter": {"pageNum": page, "pageSize": page_size},
|
|
1531
1548
|
"viewgraphKey": viewgraph_key,
|
|
1532
1549
|
"equipmentType": 0,
|
|
1533
1550
|
"associationQuery": association_query,
|
|
@@ -94,8 +94,13 @@ class WorkspaceTools(ToolBase):
|
|
|
94
94
|
normalized = result if isinstance(result, dict) else {"list": result if isinstance(result, list) else []}
|
|
95
95
|
workspace_list = normalized.get("list") if isinstance(normalized.get("list"), list) else []
|
|
96
96
|
selected_ws_id = _.selected_ws_id if _ is not None else None
|
|
97
|
+
selected_in_page = bool(
|
|
98
|
+
selected_ws_id
|
|
99
|
+
and any(isinstance(item, dict) and item.get("wsId") == selected_ws_id for item in workspace_list)
|
|
100
|
+
)
|
|
97
101
|
warnings: list[dict[str, Any]] = []
|
|
98
|
-
|
|
102
|
+
selected_workspace = None
|
|
103
|
+
if page_num == 1 and selected_ws_id and not selected_in_page:
|
|
99
104
|
try:
|
|
100
105
|
selected_workspace = self.backend.request("GET", context, f"/user/workspace/{selected_ws_id}")
|
|
101
106
|
except QingflowApiError as exc:
|
|
@@ -106,23 +111,15 @@ class WorkspaceTools(ToolBase):
|
|
|
106
111
|
"wsId": selected_ws_id,
|
|
107
112
|
"workspaceName": _.selected_ws_name if _ is not None else None,
|
|
108
113
|
}
|
|
109
|
-
if isinstance(selected_workspace, dict):
|
|
110
|
-
workspace_list.append(selected_workspace)
|
|
111
|
-
normalized["list"] = workspace_list
|
|
112
|
-
total = normalized.get("total")
|
|
113
|
-
if isinstance(total, int):
|
|
114
|
-
normalized["total"] = total + 1
|
|
115
114
|
return {
|
|
116
115
|
"profile": profile,
|
|
117
116
|
"include_external": include_external,
|
|
118
117
|
"page": normalized,
|
|
118
|
+
"selected_workspace": selected_workspace,
|
|
119
119
|
"warnings": warnings,
|
|
120
120
|
"verification": {
|
|
121
|
-
"selected_workspace_detail_verified": not warnings,
|
|
122
|
-
"selected_workspace_included":
|
|
123
|
-
selected_ws_id
|
|
124
|
-
and any(isinstance(item, dict) and item.get("wsId") == selected_ws_id for item in workspace_list)
|
|
125
|
-
),
|
|
121
|
+
"selected_workspace_detail_verified": not warnings and (selected_in_page or isinstance(selected_workspace, dict)),
|
|
122
|
+
"selected_workspace_included": selected_in_page,
|
|
126
123
|
},
|
|
127
124
|
}
|
|
128
125
|
|