@josephyan/qingflow-app-user-mcp 0.2.0-beta.1006 → 0.2.0-beta.1007
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/cli/commands/task.py +15 -9
- package/src/qingflow_mcp/cli/formatters.py +27 -2
- package/src/qingflow_mcp/cli/terminal_ui.py +28 -1
- package/src/qingflow_mcp/tools/task_context_tools.py +26 -8
package/README.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Install:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.
|
|
6
|
+
npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.1007
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.
|
|
12
|
+
npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.1007 qingflow-app-user-mcp
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -667,7 +667,9 @@ def _build_task_detail_title(task_context: dict[str, Any]) -> str:
|
|
|
667
667
|
editable_fields = data.get("editable_fields") if isinstance(data.get("editable_fields"), list) else []
|
|
668
668
|
extras = data.get("extras") if isinstance(data.get("extras"), dict) else {}
|
|
669
669
|
initiator = task.get("initiator") if isinstance(task.get("initiator"), dict) else {}
|
|
670
|
+
all_fields = record_summary.get("all_fields") if isinstance(record_summary.get("all_fields"), dict) else {}
|
|
670
671
|
core_fields = record_summary.get("core_fields") if isinstance(record_summary.get("core_fields"), dict) else {}
|
|
672
|
+
field_map = all_fields or core_fields
|
|
671
673
|
action_labels = ", ".join(TASK_ACTION_LABELS.get(str(item), str(item)) for item in available_actions if str(item).strip()) or "-"
|
|
672
674
|
lines = [
|
|
673
675
|
"待办详情",
|
|
@@ -676,6 +678,8 @@ def _build_task_detail_title(task_context: dict[str, Any]) -> str:
|
|
|
676
678
|
f"节点: {task.get('workflow_node_name') or '-'}",
|
|
677
679
|
f"发起人: {_task_initiator_label(initiator)}",
|
|
678
680
|
f"状态: {record_summary.get('apply_status') or '-'}",
|
|
681
|
+
f"申请编号: {record_summary.get('custom_apply_num') or record_summary.get('apply_num') or '-'}",
|
|
682
|
+
f"提交时间: {record_summary.get('apply_time') or '-'}",
|
|
679
683
|
f"可执行动作: {action_labels}",
|
|
680
684
|
f"可编辑字段: {len(editable_fields)}",
|
|
681
685
|
(
|
|
@@ -685,11 +689,11 @@ def _build_task_detail_title(task_context: dict[str, Any]) -> str:
|
|
|
685
689
|
f"转交 {_count_candidate_items(extras.get('transfer_candidates'))}"
|
|
686
690
|
),
|
|
687
691
|
]
|
|
688
|
-
if
|
|
692
|
+
if field_map:
|
|
689
693
|
lines.append("")
|
|
690
|
-
lines.append("
|
|
691
|
-
for key, value in
|
|
692
|
-
lines.append(f"- {key}: {
|
|
694
|
+
lines.append(f"字段值({len(field_map)}):")
|
|
695
|
+
for key, value in field_map.items():
|
|
696
|
+
lines.append(f"- {key}: {_full_display(value)}")
|
|
693
697
|
return "\n".join(lines)
|
|
694
698
|
|
|
695
699
|
|
|
@@ -711,11 +715,13 @@ def _task_has_interactive_actions(task_context: dict[str, Any]) -> bool:
|
|
|
711
715
|
return any(action != "save_only" for action in available_actions)
|
|
712
716
|
|
|
713
717
|
|
|
714
|
-
def
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
718
|
+
def _full_display(value: Any) -> str:
|
|
719
|
+
if value in (None, ""):
|
|
720
|
+
return "-"
|
|
721
|
+
if isinstance(value, list):
|
|
722
|
+
normalized = [str(item) for item in value if item not in (None, "")]
|
|
723
|
+
return " / ".join(normalized) if normalized else "-"
|
|
724
|
+
return str(value)
|
|
719
725
|
|
|
720
726
|
|
|
721
727
|
def _has_interactive_terminal(args: argparse.Namespace) -> bool:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import textwrap
|
|
4
5
|
from typing import Any, TextIO
|
|
5
6
|
|
|
6
7
|
|
|
@@ -237,15 +238,22 @@ def _format_task_get(result: dict[str, Any]) -> str:
|
|
|
237
238
|
f"App: {task.get('app_name') or '-'}",
|
|
238
239
|
f"Initiator: {initiator_label}",
|
|
239
240
|
f"Apply Status: {record_summary.get('apply_status')}",
|
|
241
|
+
f"Apply Number: {record_summary.get('custom_apply_num') or record_summary.get('apply_num') or '-'}",
|
|
242
|
+
f"Apply Time: {record_summary.get('apply_time') or '-'}",
|
|
240
243
|
f"Available Actions: {', '.join(str(item) for item in available_actions) or '-'}",
|
|
241
244
|
f"Editable Fields: {len(editable_fields)}",
|
|
242
245
|
]
|
|
243
246
|
)
|
|
247
|
+
all_fields = record_summary.get("all_fields") if isinstance(record_summary.get("all_fields"), dict) else {}
|
|
244
248
|
core_fields = record_summary.get("core_fields") if isinstance(record_summary.get("core_fields"), dict) else {}
|
|
245
|
-
if
|
|
249
|
+
if all_fields:
|
|
250
|
+
lines.append("Fields:")
|
|
251
|
+
for key, value in all_fields.items():
|
|
252
|
+
lines.extend(_format_field_line(key, value))
|
|
253
|
+
elif core_fields:
|
|
246
254
|
lines.append("Core Fields:")
|
|
247
255
|
for key, value in list(core_fields.items())[:12]:
|
|
248
|
-
lines.
|
|
256
|
+
lines.extend(_format_field_line(key, value))
|
|
249
257
|
if editable_fields:
|
|
250
258
|
lines.append("Editable Fields:")
|
|
251
259
|
for item in editable_fields[:10]:
|
|
@@ -303,6 +311,23 @@ def _format_task_workbench(result: dict[str, Any]) -> str:
|
|
|
303
311
|
return ""
|
|
304
312
|
|
|
305
313
|
|
|
314
|
+
def _format_field_line(key: Any, value: Any) -> list[str]:
|
|
315
|
+
if isinstance(value, list):
|
|
316
|
+
text = " / ".join(str(item) for item in value if item not in (None, ""))
|
|
317
|
+
else:
|
|
318
|
+
text = str(value if value not in (None, "") else "-")
|
|
319
|
+
wrapped = textwrap.wrap(
|
|
320
|
+
text,
|
|
321
|
+
width=120,
|
|
322
|
+
initial_indent=f"- {key}: ",
|
|
323
|
+
subsequent_indent=" ",
|
|
324
|
+
replace_whitespace=False,
|
|
325
|
+
drop_whitespace=False,
|
|
326
|
+
break_long_words=True,
|
|
327
|
+
)
|
|
328
|
+
return wrapped or [f"- {key}: -"]
|
|
329
|
+
|
|
330
|
+
|
|
306
331
|
def _format_task_associated_report_detail(result: dict[str, Any]) -> str:
|
|
307
332
|
data = result.get("data") if isinstance(result.get("data"), dict) else {}
|
|
308
333
|
selection = data.get("selection") if isinstance(data.get("selection"), dict) else {}
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import os
|
|
4
4
|
import select
|
|
5
5
|
import shutil
|
|
6
|
+
import textwrap
|
|
6
7
|
from dataclasses import dataclass
|
|
7
8
|
from typing import Generic, Sequence, TextIO, TypeVar
|
|
8
9
|
|
|
@@ -165,7 +166,33 @@ def _truncate_line(text: str, *, width: int) -> str:
|
|
|
165
166
|
|
|
166
167
|
def _render_multiline_text(text: str, *, width: int) -> list[str]:
|
|
167
168
|
parts = text.splitlines() or [text]
|
|
168
|
-
|
|
169
|
+
rendered: list[str] = []
|
|
170
|
+
wrap_width = max(1, width)
|
|
171
|
+
for part in parts:
|
|
172
|
+
if not part:
|
|
173
|
+
rendered.append("")
|
|
174
|
+
continue
|
|
175
|
+
initial_indent = ""
|
|
176
|
+
subsequent_indent = ""
|
|
177
|
+
stripped = part.lstrip()
|
|
178
|
+
if stripped.startswith("- "):
|
|
179
|
+
leading_spaces = len(part) - len(stripped)
|
|
180
|
+
initial_indent = part[:leading_spaces] + "- "
|
|
181
|
+
subsequent_indent = part[:leading_spaces] + " "
|
|
182
|
+
content = stripped[2:]
|
|
183
|
+
else:
|
|
184
|
+
content = part
|
|
185
|
+
wrapped = textwrap.wrap(
|
|
186
|
+
content,
|
|
187
|
+
width=wrap_width,
|
|
188
|
+
initial_indent=initial_indent,
|
|
189
|
+
subsequent_indent=subsequent_indent,
|
|
190
|
+
replace_whitespace=False,
|
|
191
|
+
drop_whitespace=False,
|
|
192
|
+
break_long_words=True,
|
|
193
|
+
)
|
|
194
|
+
rendered.extend(wrapped or [""])
|
|
195
|
+
return rendered
|
|
169
196
|
|
|
170
197
|
|
|
171
198
|
def _read_key(input_stream: TextIO) -> str:
|
|
@@ -1762,6 +1762,7 @@ class TaskContextTools(ToolBase):
|
|
|
1762
1762
|
"apply_time": record.get("apply_time"),
|
|
1763
1763
|
"last_update_time": record.get("last_update_time"),
|
|
1764
1764
|
"core_fields": self._task_record_core_fields(record.get("answers") or []),
|
|
1765
|
+
"all_fields": self._task_record_all_fields(record.get("answers") or []),
|
|
1765
1766
|
},
|
|
1766
1767
|
"available_actions": available_actions,
|
|
1767
1768
|
"editable_fields": [
|
|
@@ -1921,9 +1922,21 @@ class TaskContextTools(ToolBase):
|
|
|
1921
1922
|
return None
|
|
1922
1923
|
|
|
1923
1924
|
def _task_record_core_fields(self, answers: Any, *, limit: int = 12) -> dict[str, Any]:
|
|
1925
|
+
return self._task_record_field_map(answers, limit=limit, truncate_text=160)
|
|
1926
|
+
|
|
1927
|
+
def _task_record_all_fields(self, answers: Any) -> dict[str, Any]:
|
|
1928
|
+
return self._task_record_field_map(answers, limit=None, truncate_text=None)
|
|
1929
|
+
|
|
1930
|
+
def _task_record_field_map(
|
|
1931
|
+
self,
|
|
1932
|
+
answers: Any,
|
|
1933
|
+
*,
|
|
1934
|
+
limit: int | None,
|
|
1935
|
+
truncate_text: int | None,
|
|
1936
|
+
) -> dict[str, Any]:
|
|
1924
1937
|
if not isinstance(answers, list):
|
|
1925
1938
|
return {}
|
|
1926
|
-
|
|
1939
|
+
field_map: dict[str, Any] = {}
|
|
1927
1940
|
for answer in answers:
|
|
1928
1941
|
if not isinstance(answer, dict):
|
|
1929
1942
|
continue
|
|
@@ -1943,19 +1956,24 @@ class TaskContextTools(ToolBase):
|
|
|
1943
1956
|
value = values[0] if len(values) == 1 else values
|
|
1944
1957
|
if value in (None, "", []):
|
|
1945
1958
|
continue
|
|
1946
|
-
|
|
1947
|
-
if len(
|
|
1959
|
+
field_map[str(title)] = self._compact_task_value(value, truncate_text=truncate_text)
|
|
1960
|
+
if limit is not None and len(field_map) >= limit:
|
|
1948
1961
|
break
|
|
1949
|
-
return
|
|
1962
|
+
return field_map
|
|
1950
1963
|
|
|
1951
|
-
def _compact_task_value(self, value: Any) -> Any:
|
|
1964
|
+
def _compact_task_value(self, value: Any, *, truncate_text: int | None = 160) -> Any:
|
|
1952
1965
|
if isinstance(value, list):
|
|
1953
|
-
|
|
1966
|
+
items = [self._compact_task_value(item, truncate_text=truncate_text) for item in value]
|
|
1967
|
+
if truncate_text is not None:
|
|
1968
|
+
return items[:8]
|
|
1969
|
+
return items
|
|
1954
1970
|
text = re.sub(r"<[^>]+>", " ", str(value))
|
|
1955
1971
|
text = re.sub(r"\s+", " ", text).strip()
|
|
1956
|
-
if len(text) <=
|
|
1972
|
+
if truncate_text is None or len(text) <= truncate_text:
|
|
1957
1973
|
return text
|
|
1958
|
-
|
|
1974
|
+
if truncate_text <= 3:
|
|
1975
|
+
return text[:truncate_text]
|
|
1976
|
+
return text[: truncate_text - 3].rstrip() + "..."
|
|
1959
1977
|
|
|
1960
1978
|
def _compact_task_editable_field(self, field: dict[str, Any], update_schema: dict[str, Any]) -> dict[str, Any]:
|
|
1961
1979
|
payload_template = update_schema.get("payload_template") if isinstance(update_schema.get("payload_template"), dict) else {}
|