@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 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.1006
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.1006 qingflow-app-user-mcp
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@josephyan/qingflow-app-user-mcp",
3
- "version": "0.2.0-beta.1006",
3
+ "version": "0.2.0-beta.1007",
4
4
  "description": "Operational end-user MCP for Qingflow records, tasks, comments, and directory workflows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qingflow-mcp"
7
- version = "0.2.0b1006"
7
+ version = "0.2.0b1007"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -5,7 +5,7 @@ from pathlib import Path
5
5
 
6
6
  __all__ = ["__version__"]
7
7
 
8
- _FALLBACK_VERSION = "0.2.0b1006"
8
+ _FALLBACK_VERSION = "0.2.0b1007"
9
9
 
10
10
 
11
11
  def _resolve_local_pyproject_version() -> str | None:
@@ -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 core_fields:
692
+ if field_map:
689
693
  lines.append("")
690
- lines.append("摘要字段:")
691
- for key, value in list(core_fields.items())[:6]:
692
- lines.append(f"- {key}: {_short_display(value)}")
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 _short_display(value: Any, *, limit: int = 60) -> str:
715
- text = str(value if value not in (None, "") else "-")
716
- if len(text) <= limit:
717
- return text
718
- return text[: limit - 1] + ""
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 core_fields:
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.append(f"- {key}: {value}")
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
- return [_truncate_line(part, width=width) for part in parts]
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
- core_fields: dict[str, Any] = {}
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
- core_fields[str(title)] = self._compact_task_value(value)
1947
- if len(core_fields) >= limit:
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 core_fields
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
- return [self._compact_task_value(item) for item in value[:8]]
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) <= 160:
1972
+ if truncate_text is None or len(text) <= truncate_text:
1957
1973
  return text
1958
- return text[:157].rstrip() + "..."
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 {}