@qingflow-tech/qingflow-cli 1.1.48-beta.2 → 1.1.48-beta.4
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/skills/qingflow-cli/SKILL.md +9 -9
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +1 -1
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +1 -1
- package/skills/qingflow-cli/reference/data/README.md +2 -2
- package/skills/qingflow-cli/reference/record/{QINGFLOW_CLI_LEGACY_RECORD_RETRIEVAL_WORKFLOW.md → QINGFLOW_CLI_RECORD_SPECIALIZED_WORKFLOW.md} +2 -2
- 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/data_tools.py +15 -2
- 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.48-beta.
|
|
10
|
+
npm install @qingflow-tech/qingflow-cli@1.1.48-beta.4
|
|
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.48-beta.
|
|
24
|
+
npx -y -p @qingflow-tech/qingflow-cli@1.1.48-beta.4 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.48-beta.
|
|
65
|
+
npx -y -p @qingflow-tech/qingflow-cli@1.1.48-beta.4 qingflow-skills install --agent codex --scope user --copy
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
`qingflow-skills install` 默认使用 symlink,且遇到同名 skill 不会覆盖;只有显式传入 `--force` 才会替换。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qingflow-tech/qingflow-cli",
|
|
3
|
-
"version": "1.1.48-beta.
|
|
3
|
+
"version": "1.1.48-beta.4",
|
|
4
4
|
"description": "Human-friendly Qingflow command line interface for auth, record operations, import, tasks, and stable builder flows.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/pyproject.toml
CHANGED
|
@@ -9,13 +9,13 @@ description: |
|
|
|
9
9
|
|
|
10
10
|
> **Skill 版本**:`qingflow-skills-2026.09.22.01`。CLI 实际版本与安装路径以 `qingflow --json version` 为准。
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
本 Skill 负责 CLI 入口、文件约定和业务分流。已知 `app_key` 时直接执行 `data query/get`;字段能力、权限范围与下一步动作以当前工具 schema、data 响应和 `hints` 为准。
|
|
13
13
|
|
|
14
14
|
## CLI 执行与 JSON 落盘
|
|
15
15
|
|
|
16
16
|
1. 已知业务目标时直接执行目标命令,不固定前置 `auth whoami`、`workspace get` 或 `workspace select`。只有业务命令明确返回未登录、会话失效或工作区错误后,才恢复上下文并重试原命令。
|
|
17
17
|
2. 自动化始终使用 `--json`。`data query/get`、task 查询、record 日志/附件上下文、import 与 builder 大响应必须写入本地文件,禁止把整段 JSON 或 CSV 打到上下文。
|
|
18
|
-
3. `where`、`sort`、`columns`、`items`、`fields` 等结构化参数写入独立 JSON 文件,再通过 `--*-file`
|
|
18
|
+
3. `where`、`sort`、`columns`、`items`、`fields` 等结构化参数写入独立 JSON 文件,再通过 `--*-file` 传入。工具已暴露参数 schema 时直接使用;只有命令缺失或参数被拒绝时才读取对应 `--help`。
|
|
19
19
|
4. 多任务或目录可能变化时使用绝对路径;单线程本地执行至少先固定 `pwd`。解析时只读取目标字段,不要 `cat` 大文件。
|
|
20
20
|
5. `--json` 模式的业务错误通常也在 stdout JSON 信封中;stderr 主要用于 argparse usage。写命令返回已执行、部分成功或状态不明时,不得重放原请求。
|
|
21
21
|
|
|
@@ -46,8 +46,8 @@ echo "$QINGFLOW_CREDENTIAL" | qingflow --profile default auth use-credential \
|
|
|
46
46
|
| 待办、已办、抄送、审批、转交或退回 | `qingflow task ...` | [任务上下文 SOP](./reference/task/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md) |
|
|
47
47
|
| Excel 模板、文件校验、修复、启动导入与状态 | `qingflow import ...` / `record schema import` | [批量导入 SOP](./reference/record/QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md) |
|
|
48
48
|
| 应用、表单、字段、视图、报表、门户与流程搭建 | `qingflow builder ...` | [Builder 总导航](./reference/builder/README.md) |
|
|
49
|
-
| 完整数据/流程日志 | `qingflow record logs` | [record 专项参考](./reference/record/
|
|
50
|
-
| 附件、图片与详情页上下文 | `qingflow record get` | [record 专项参考](./reference/record/
|
|
49
|
+
| 完整数据/流程日志 | `qingflow record logs` | [record 专项参考](./reference/record/QINGFLOW_CLI_RECORD_SPECIALIZED_WORKFLOW.md) |
|
|
50
|
+
| 附件、图片与详情页上下文 | `qingflow record get` | [record 专项参考](./reference/record/QINGFLOW_CLI_RECORD_SPECIALIZED_WORKFLOW.md) |
|
|
51
51
|
| 代码块字段执行 | `qingflow record code-block-run` | [代码集成说明](./reference/builder/code-integrations/README.md) |
|
|
52
52
|
| 安装、版本或命令能力确认 | `qingflow --json version` / `qingflow ... --help` | [命令索引](./reference/00-INDEX.md) |
|
|
53
53
|
|
|
@@ -97,7 +97,7 @@ qingflow --json data query \
|
|
|
97
97
|
|
|
98
98
|
### 分页
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
先请求第一页,并按响应 `hints` 执行:`RESULT_IS_PARTIAL` 时保持筛选与排序继续下一页,`RESULT_IS_COMPLETE` 时立即停止;不凭总数猜页数。
|
|
101
101
|
|
|
102
102
|
```bash
|
|
103
103
|
qingflow --json data query --app-key APP_KEY --page 1 --page-size 100 \
|
|
@@ -132,11 +132,11 @@ qingflow --json data query \
|
|
|
132
132
|
- 导出/下载只有用户明确需要文件时才使用,并按对应命令要求提供视图上下文;不要把导出当作普通查询或统计分析默认入口。
|
|
133
133
|
- 查看视图时,`baseInfo` 或 `viewConfig` 只有 40002 / 40027 / 404 这类权限/缺失边界可用另一侧降级,非权限类后端错误不隐藏;完整规则见 Builder 视图文档。
|
|
134
134
|
|
|
135
|
-
## record
|
|
135
|
+
## record 专项
|
|
136
136
|
|
|
137
|
-
`record`
|
|
137
|
+
`record` 用于完整数据/流程日志、附件和图片上下文、代码块字段执行,以及用户指定的 `record` 操作。
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
普通应用数据使用 `data`。不得改用 `record list/access/get/insert/update/delete` 绕过 data 返回的权限、字段能力或校验结果。日志与附件操作阅读 [record 专项参考](./reference/record/QINGFLOW_CLI_RECORD_SPECIALIZED_WORKFLOW.md);`record delete` 提交不要传 `custom:*`、`list_type`,细则见 [record 删除参考](./reference/record/QINGFLOW_CLI_RECORD_DELETE_WORKFLOW.md)。
|
|
140
140
|
|
|
141
141
|
## 关联入口
|
|
142
142
|
|
|
@@ -146,6 +146,6 @@ qingflow --json data query \
|
|
|
146
146
|
- [批量导入 SOP](./reference/record/QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md)
|
|
147
147
|
- [数据分析说明](./reference/record/analysis/README.md)
|
|
148
148
|
- [Builder 总导航](./reference/builder/README.md)
|
|
149
|
-
- [record 专项参考](./reference/record/
|
|
149
|
+
- [record 专项参考](./reference/record/QINGFLOW_CLI_RECORD_SPECIALIZED_WORKFLOW.md)
|
|
150
150
|
|
|
151
151
|
维护时用 `qingflow --json version`、`qingflow data --help` 与对应专项 `--help` 复核,不根据历史文档补造参数。
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
普通应用数据查询默认使用 `data query/get`。完整流程、JSON 形状、分页与分析边界见 [core/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md](./core/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md)。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
需要完整日志、附件和图片上下文时,使用 [record 专项参考](./record/QINGFLOW_CLI_RECORD_SPECIALIZED_WORKFLOW.md)。
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **用途**:使用视图无关的 `data query/get` 完成普通应用数据查询、筛选、排序、分页和单条读取。
|
|
4
4
|
> **边界**:本文只给流程与 JSON 形状;操作符、字段类型、权限范围和动态限制以当前 `--help`、`schema`、`field_capabilities`、`access` 与 `hints` 为准。
|
|
5
|
-
> **record
|
|
5
|
+
> **record 专项**:需要完整日志、附件/图片上下文时,阅读 [record 专项参考](../record/QINGFLOW_CLI_RECORD_SPECIALIZED_WORKFLOW.md)。
|
|
6
6
|
|
|
7
7
|
## 推荐顺序
|
|
8
8
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
- `qingflow data update`
|
|
9
9
|
- `qingflow data delete`
|
|
10
10
|
|
|
11
|
-
参数、JSON
|
|
11
|
+
参数、JSON 文件形状和可选项必须来自当前工具 schema、当次 `--help` 或响应字段能力,不得从其他命令推测。
|
|
12
12
|
|
|
13
13
|
## 字段能力合同
|
|
14
14
|
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
- 定位目标记录时先 query,再按需 get。字段只使用当前 schema 与字段能力允许的 id、能力和形状。
|
|
24
24
|
- insert、update、delete 会改变真实业务数据。删除、覆盖已有值或批量写入前,必须确认目标和影响范围。
|
|
25
25
|
- 返回后先检查 `write_executed`、`retry_safe`、字段错误和 hints。已执行、部分成功或状态不明时不得重放原请求。
|
|
26
|
-
- data
|
|
26
|
+
- data 命令失败时只修正响应明确指出的问题,不得切换其他数据入口绕过权限或校验。
|
|
27
27
|
|
|
28
28
|
## 任务分流
|
|
29
29
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Qingflow CLI:record
|
|
1
|
+
# Qingflow CLI:record 日志与附件参考
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> **适用范围**:任务需要完整数据/流程日志、附件或图片上下文时使用。普通应用数据查询使用 [`data query/get`](../core/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md)。
|
|
4
4
|
|
|
5
5
|
## record 查询链
|
|
6
6
|
|
|
@@ -2008,6 +2008,12 @@ class AiBuilderFacade:
|
|
|
2008
2008
|
"error_code": None,
|
|
2009
2009
|
"recoverable": False,
|
|
2010
2010
|
"message": "resolved members",
|
|
2011
|
+
"pagination": {
|
|
2012
|
+
**(listed.get("data", {}).get("pagination", {})),
|
|
2013
|
+
"page": page_num,
|
|
2014
|
+
"page_size": page_size,
|
|
2015
|
+
"returned_items": len(items),
|
|
2016
|
+
},
|
|
2011
2017
|
"normalized_args": normalized_args,
|
|
2012
2018
|
"missing_fields": [],
|
|
2013
2019
|
"allowed_values": {},
|
|
@@ -2073,6 +2079,13 @@ class AiBuilderFacade:
|
|
|
2073
2079
|
"error_code": None,
|
|
2074
2080
|
"recoverable": False,
|
|
2075
2081
|
"message": "resolved roles",
|
|
2082
|
+
"pagination": {
|
|
2083
|
+
"page": page_num,
|
|
2084
|
+
"page_size": page_size,
|
|
2085
|
+
"returned_items": len(items),
|
|
2086
|
+
"reported_total": page.get("total"),
|
|
2087
|
+
"page_amount": page.get("pageAmount"),
|
|
2088
|
+
},
|
|
2076
2089
|
"normalized_args": normalized_args,
|
|
2077
2090
|
"missing_fields": [],
|
|
2078
2091
|
"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):
|
|
@@ -236,7 +236,11 @@ def _format_workspace_list(result: dict[str, Any]) -> str:
|
|
|
236
236
|
str(item.get("remark") or ""),
|
|
237
237
|
]
|
|
238
238
|
)
|
|
239
|
-
|
|
239
|
+
text = _render_titled_table("Workspaces", ["ws_id", "name", "remark"], rows)
|
|
240
|
+
selected = result.get("selected_workspace")
|
|
241
|
+
if isinstance(selected, dict):
|
|
242
|
+
text += f"Selected Workspace: {selected.get('wsId')} / {selected.get('workspaceName') or '-'}\n"
|
|
243
|
+
return text
|
|
240
244
|
|
|
241
245
|
|
|
242
246
|
def _format_workspace_get(result: dict[str, Any]) -> str:
|
|
@@ -448,11 +452,9 @@ def _format_record_list(result: dict[str, Any]) -> str:
|
|
|
448
452
|
lines.append(f"- returned_count: {lookup.get('returned_count')}")
|
|
449
453
|
lines.append(f"- truncated: {lookup.get('truncated')}")
|
|
450
454
|
lines.append(f"Returned Records: {len(items)}")
|
|
451
|
-
for item in items
|
|
455
|
+
for item in items:
|
|
452
456
|
if isinstance(item, dict):
|
|
453
457
|
lines.append(json.dumps(item, ensure_ascii=False))
|
|
454
|
-
if len(items) > 10:
|
|
455
|
-
lines.append(f"... {len(items) - 10} more")
|
|
456
458
|
_append_warnings(lines, result.get("warnings"))
|
|
457
459
|
_append_verification(lines, result.get("verification"))
|
|
458
460
|
return "\n".join(lines) + "\n"
|
|
@@ -513,10 +515,11 @@ def _format_record_candidates(result: dict[str, Any]) -> str:
|
|
|
513
515
|
f"page_size={pagination.get('page_size')} / "
|
|
514
516
|
f"returned={pagination.get('returned_items')} / "
|
|
515
517
|
f"total={pagination.get('reported_total')} / "
|
|
516
|
-
f"pages={pagination.get('page_amount')}"
|
|
518
|
+
f"pages={pagination.get('page_amount')} / "
|
|
519
|
+
f"has_more={pagination.get('has_more')}"
|
|
517
520
|
)
|
|
518
521
|
lines.append(f"Candidates: {len(items)}")
|
|
519
|
-
for item in items
|
|
522
|
+
for item in items:
|
|
520
523
|
if not isinstance(item, dict):
|
|
521
524
|
lines.append(f"- {item}")
|
|
522
525
|
continue
|
|
@@ -538,8 +541,6 @@ def _format_record_candidates(result: dict[str, Any]) -> str:
|
|
|
538
541
|
if source_labels:
|
|
539
542
|
parts.append("sources=" + ",".join(source_labels))
|
|
540
543
|
lines.append("- " + " / ".join(parts))
|
|
541
|
-
if len(items) > 20:
|
|
542
|
-
lines.append(f"... {len(items) - 20} more")
|
|
543
544
|
_append_warnings(lines, result.get("warnings"))
|
|
544
545
|
_append_verification(lines, result.get("verification"))
|
|
545
546
|
return "\n".join(lines) + "\n"
|
|
@@ -1036,11 +1037,9 @@ def _format_task_associated_report_detail(result: dict[str, Any]) -> str:
|
|
|
1036
1037
|
result_payload = data.get("result") if isinstance(data.get("result"), dict) else {}
|
|
1037
1038
|
items = result_payload.get("items") if isinstance(result_payload.get("items"), list) else []
|
|
1038
1039
|
lines.append(f"Returned Records: {len(items)}")
|
|
1039
|
-
for item in items
|
|
1040
|
+
for item in items:
|
|
1040
1041
|
if isinstance(item, dict):
|
|
1041
1042
|
lines.append(json.dumps(item, ensure_ascii=False))
|
|
1042
|
-
if len(items) > 10:
|
|
1043
|
-
lines.append(f"... {len(items) - 10} more")
|
|
1044
1043
|
elif result_type == "chart_data":
|
|
1045
1044
|
result_payload = data.get("result") if isinstance(data.get("result"), dict) else {}
|
|
1046
1045
|
summary = result_payload.get("summary") if isinstance(result_payload.get("summary"), dict) else {}
|
|
@@ -1048,11 +1047,9 @@ def _format_task_associated_report_detail(result: dict[str, Any]) -> str:
|
|
|
1048
1047
|
if summary:
|
|
1049
1048
|
lines.append(f"Summary: {json.dumps(summary, ensure_ascii=False)}")
|
|
1050
1049
|
lines.append(f"Rows: {len(rows)}")
|
|
1051
|
-
for row in rows
|
|
1050
|
+
for row in rows:
|
|
1052
1051
|
if isinstance(row, dict):
|
|
1053
1052
|
lines.append(json.dumps(row, ensure_ascii=False))
|
|
1054
|
-
if len(rows) > 10:
|
|
1055
|
-
lines.append(f"... {len(rows) - 10} more")
|
|
1056
1053
|
resolved_filters = context.get("resolved_filters") if isinstance(context.get("resolved_filters"), list) else []
|
|
1057
1054
|
if resolved_filters:
|
|
1058
1055
|
lines.append(f"Resolved Filters: {len(resolved_filters)}")
|
|
@@ -1267,6 +1264,9 @@ def _format_export_direct(result: dict[str, Any]) -> str:
|
|
|
1267
1264
|
|
|
1268
1265
|
def _format_builder_summary(result: dict[str, Any]) -> str:
|
|
1269
1266
|
lines = []
|
|
1267
|
+
pagination = result.get("pagination")
|
|
1268
|
+
if isinstance(pagination, dict):
|
|
1269
|
+
lines.append("Pagination: " + json.dumps(pagination, ensure_ascii=False))
|
|
1270
1270
|
emitted: set[str] = set()
|
|
1271
1271
|
for key, label in (
|
|
1272
1272
|
("status", "Status"),
|
|
@@ -1316,10 +1316,11 @@ def _format_builder_summary(result: dict[str, Any]) -> str:
|
|
|
1316
1316
|
value = data.get(key)
|
|
1317
1317
|
if isinstance(value, list):
|
|
1318
1318
|
lines.append(f"{key}: {len(value)}")
|
|
1319
|
-
|
|
1319
|
+
visible_items = value if isinstance(pagination, dict) else value[:8]
|
|
1320
|
+
for item in visible_items:
|
|
1320
1321
|
lines.append("- " + _builder_item_summary(item))
|
|
1321
|
-
if len(value) >
|
|
1322
|
-
lines.append(f"... {len(value) -
|
|
1322
|
+
if len(value) > len(visible_items):
|
|
1323
|
+
lines.append(f"... {len(value) - len(visible_items)} more")
|
|
1323
1324
|
if key == "apps":
|
|
1324
1325
|
uri_lines = [
|
|
1325
1326
|
line
|
|
@@ -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:
|
|
@@ -207,6 +207,8 @@ class DataTools(ToolBase):
|
|
|
207
207
|
"ok": True, "status": "needs_scope", "app_key": app_key, "format": "csv", "row_count": 0,
|
|
208
208
|
"complete": False, "truncated": True, "safe_for_final_conclusion": False, "files": [],
|
|
209
209
|
"hints": probe.get("hints", []),
|
|
210
|
+
"schema": probe.get("schema", {}),
|
|
211
|
+
"field_capabilities": probe.get("field_capabilities", []),
|
|
210
212
|
"scope": {"reported_total": total, "row_threshold": DATA_UNBOUNDED_ROW_THRESHOLD},
|
|
211
213
|
}, profile, ws_id)
|
|
212
214
|
schema = probe.get("schema") if isinstance(probe.get("schema"), dict) else {}
|
|
@@ -216,15 +218,25 @@ class DataTools(ToolBase):
|
|
|
216
218
|
columns = [(str(field.get("field_id")), str(field.get("title") or field.get("field_id"))) for field in business_fields]
|
|
217
219
|
header = ["record_id"] + [f"{title}__field_{field_id}" for field_id, title in columns]
|
|
218
220
|
run_dir = Path.home() / ".qingflow" / "data-exports" / datetime.now().strftime("%Y%m%d-%H%M%S-%f")
|
|
221
|
+
latest_hints = probe.get("hints", [])
|
|
222
|
+
latest_schema = schema
|
|
223
|
+
latest_capabilities = probe.get("field_capabilities", [])
|
|
219
224
|
|
|
220
225
|
def fetch_page(page: int, page_size: int) -> JSONObject:
|
|
226
|
+
nonlocal latest_hints, latest_schema, latest_capabilities
|
|
221
227
|
request_body = dict(body)
|
|
222
|
-
|
|
228
|
+
# 每页都请求 schema,使最后一页的后端原始 hints 同时准确表达完整性和字段能力。
|
|
229
|
+
request_body.update({"page": page, "page_size": page_size, "include_schema": True})
|
|
223
230
|
response = self._request_data_plane(
|
|
224
231
|
context, "POST", self._base_path(app_key) + "/query", json_body=request_body
|
|
225
232
|
)
|
|
226
233
|
if response.get("ok") is False:
|
|
227
234
|
raise CsvPageFailure(response)
|
|
235
|
+
latest_hints = response.get("hints", [])
|
|
236
|
+
if isinstance(response.get("schema"), dict):
|
|
237
|
+
latest_schema = response["schema"]
|
|
238
|
+
if isinstance(response.get("field_capabilities"), list):
|
|
239
|
+
latest_capabilities = response["field_capabilities"]
|
|
228
240
|
return response
|
|
229
241
|
|
|
230
242
|
def project_row(record: JSONObject) -> list[str]:
|
|
@@ -239,7 +251,8 @@ class DataTools(ToolBase):
|
|
|
239
251
|
page_error = exported.pop("page_error", None)
|
|
240
252
|
result: JSONObject = {
|
|
241
253
|
"ok": True, "status": "success" if exported["complete"] else "partial", "app_key": app_key,
|
|
242
|
-
"format": "csv", "local_dir": str(run_dir), "fields": business_fields,
|
|
254
|
+
"format": "csv", "local_dir": str(run_dir), "fields": business_fields,
|
|
255
|
+
"schema": latest_schema, "field_capabilities": latest_capabilities, "hints": latest_hints,
|
|
243
256
|
**exported,
|
|
244
257
|
}
|
|
245
258
|
if isinstance(page_error, dict):
|
|
@@ -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
|
)
|
|
@@ -13279,6 +13300,9 @@ class RecordTools(ToolBase):
|
|
|
13279
13300
|
*,
|
|
13280
13301
|
keyword: str,
|
|
13281
13302
|
state: LookupResolutionState,
|
|
13303
|
+
page_num: int = 1,
|
|
13304
|
+
page_size: int = LOOKUP_RELATION_FILTER_PAGE_SIZE,
|
|
13305
|
+
pagination: JSONObject | None = None,
|
|
13282
13306
|
) -> list[JSONObject]:
|
|
13283
13307
|
"""执行内部辅助逻辑。"""
|
|
13284
13308
|
scope = field.member_select_scope if isinstance(field.member_select_scope, dict) else {}
|
|
@@ -13286,7 +13310,9 @@ class RecordTools(ToolBase):
|
|
|
13286
13310
|
isinstance(scope.get("externalMemberList"), list) and scope.get("externalMemberList")
|
|
13287
13311
|
or isinstance(scope.get("externalDepartList"), list) and scope.get("externalDepartList")
|
|
13288
13312
|
)
|
|
13289
|
-
payload = self._build_lookup_scope_payload(
|
|
13313
|
+
payload = self._build_lookup_scope_payload(
|
|
13314
|
+
state, keyword=keyword, que_id=field.que_id, page_num=page_num, page_size=page_size,
|
|
13315
|
+
)
|
|
13290
13316
|
route = f"/data/que/{field.que_id}/external/memberSelectScope" if use_external else f"/data/que/{field.que_id}/memberSelectScope"
|
|
13291
13317
|
try:
|
|
13292
13318
|
result = self.backend.request("POST", context, route, json_body=payload)
|
|
@@ -13299,7 +13325,10 @@ class RecordTools(ToolBase):
|
|
|
13299
13325
|
details={"field": _field_ref_payload(field), "received_value": keyword, "candidate_error": exc.to_dict()},
|
|
13300
13326
|
)
|
|
13301
13327
|
self._raise_candidate_lookup_failed(kind="member", field=field, value=keyword, error=exc)
|
|
13302
|
-
|
|
13328
|
+
items = self._normalize_backend_member_candidates(result, external=use_external)
|
|
13329
|
+
if pagination is not None:
|
|
13330
|
+
pagination.update(_candidate_page_metadata(result, page_num=page_num, page_size=page_size, returned_items=len(items)))
|
|
13331
|
+
return items
|
|
13303
13332
|
|
|
13304
13333
|
def _member_confirmation_candidates(self, candidates: list[JSONObject]) -> list[JSONObject]:
|
|
13305
13334
|
"""执行内部辅助逻辑。"""
|
|
@@ -13568,16 +13597,24 @@ class RecordTools(ToolBase):
|
|
|
13568
13597
|
*,
|
|
13569
13598
|
keyword: str,
|
|
13570
13599
|
state: LookupResolutionState,
|
|
13600
|
+
page_num: int = 1,
|
|
13601
|
+
page_size: int = LOOKUP_RELATION_FILTER_PAGE_SIZE,
|
|
13602
|
+
pagination: JSONObject | None = None,
|
|
13571
13603
|
) -> list[JSONObject]:
|
|
13572
13604
|
"""执行内部辅助逻辑。"""
|
|
13573
13605
|
scope = field.dept_select_scope if isinstance(field.dept_select_scope, dict) else {}
|
|
13574
13606
|
external = bool(
|
|
13575
13607
|
isinstance(scope.get("externalDepartList"), list) and scope.get("externalDepartList")
|
|
13576
13608
|
)
|
|
13577
|
-
payload = self._build_lookup_scope_payload(
|
|
13609
|
+
payload = self._build_lookup_scope_payload(
|
|
13610
|
+
state, keyword=keyword, que_id=field.que_id, page_num=page_num, page_size=page_size,
|
|
13611
|
+
)
|
|
13578
13612
|
payload["deptScope"] = "EXTERNAL" if external else "INTERNAL"
|
|
13579
13613
|
result = self.backend.request("POST", context, "/data/deptSelectScope/search", json_body=payload)
|
|
13580
|
-
|
|
13614
|
+
items = self._normalize_backend_department_candidates(result, external=external)
|
|
13615
|
+
if pagination is not None:
|
|
13616
|
+
pagination.update(_candidate_page_metadata(result, page_num=page_num, page_size=page_size, returned_items=len(items)))
|
|
13617
|
+
return items
|
|
13581
13618
|
|
|
13582
13619
|
def _department_confirmation_candidates(self, candidates: list[JSONObject]) -> list[JSONObject]:
|
|
13583
13620
|
"""执行内部辅助逻辑。"""
|
|
@@ -17846,6 +17883,28 @@ def _normalize_public_record_rows(rows: list[JSONValue]) -> list[JSONObject]:
|
|
|
17846
17883
|
return normalized_rows
|
|
17847
17884
|
|
|
17848
17885
|
|
|
17886
|
+
def _candidate_page_metadata(
|
|
17887
|
+
payload: JSONValue, *, page_num: int, page_size: int, returned_items: int,
|
|
17888
|
+
) -> JSONObject:
|
|
17889
|
+
page = payload if isinstance(payload, dict) else {}
|
|
17890
|
+
total = _coerce_count(page.get("total"))
|
|
17891
|
+
page_amount = _coerce_count(page.get("pageAmount"))
|
|
17892
|
+
# Some member-scope endpoints return rows without a total. A full page is not a total.
|
|
17893
|
+
if total is None and returned_items < page_size and (page_num == 1 or returned_items > 0):
|
|
17894
|
+
total = (page_num - 1) * page_size + returned_items
|
|
17895
|
+
if page_amount is None and total is not None:
|
|
17896
|
+
page_amount = (total + page_size - 1) // page_size
|
|
17897
|
+
has_more = page_num < page_amount if page_amount is not None else returned_items >= page_size
|
|
17898
|
+
return {
|
|
17899
|
+
"page": page_num,
|
|
17900
|
+
"page_size": page_size,
|
|
17901
|
+
"returned_items": returned_items,
|
|
17902
|
+
"reported_total": total,
|
|
17903
|
+
"page_amount": page_amount,
|
|
17904
|
+
"has_more": has_more,
|
|
17905
|
+
}
|
|
17906
|
+
|
|
17907
|
+
|
|
17849
17908
|
def _build_record_list_lookup_payload(
|
|
17850
17909
|
*,
|
|
17851
17910
|
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
|
|