@josephyan/qingflow-app-user-mcp 0.2.0-beta.995 → 0.2.0-beta.996

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.995
6
+ npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.996
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.995 qingflow-app-user-mcp
12
+ npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.996 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.995",
3
+ "version": "0.2.0-beta.996",
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.0b995"
7
+ version = "0.2.0b996"
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.0b995"
8
+ _FALLBACK_VERSION = "0.2.0b996"
9
9
 
10
10
 
11
11
  def _resolve_local_pyproject_version() -> str | None:
@@ -40,7 +40,11 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
40
40
  action.add_argument("--action", required=True)
41
41
  action.add_argument("--payload-file")
42
42
  action.add_argument("--fields-file")
43
- action.set_defaults(handler=_handle_action, format_hint="")
43
+ action.set_defaults(
44
+ handler=_handle_action,
45
+ format_hint="task_action_execute",
46
+ hide_effective_context_line=True,
47
+ )
44
48
 
45
49
  report = task_subparsers.add_parser("report", help="读取待办关联报表详情;推荐直接传 --task-id")
46
50
  report.add_argument("--task-id")
@@ -249,6 +249,30 @@ def _format_task_get(result: dict[str, Any]) -> str:
249
249
  return "\n".join(lines) + "\n"
250
250
 
251
251
 
252
+ def _format_task_action(result: dict[str, Any]) -> str:
253
+ data = result.get("data") if isinstance(result.get("data"), dict) else {}
254
+ action = str(data.get("action") or "").strip().lower()
255
+ status = str(result.get("status") or "").strip().lower()
256
+
257
+ if status == "failed" or result.get("ok") is False:
258
+ lines = [_task_action_failure_label(action)]
259
+ reason = _task_action_failure_reason(result)
260
+ if reason:
261
+ lines.append(f"原因:{reason}")
262
+ debug_lines = _task_action_debug_lines(result)
263
+ if debug_lines:
264
+ lines.append("调试信息:")
265
+ lines.extend(f"- {line}" for line in debug_lines)
266
+ return "\n".join(lines) + "\n"
267
+
268
+ if status == "partial_success":
269
+ lines = [_task_action_success_label(action)]
270
+ lines.append(f"说明:{_task_action_partial_success_message(result)}")
271
+ return "\n".join(lines) + "\n"
272
+
273
+ return _task_action_success_label(action) + "\n"
274
+
275
+
252
276
  def _format_task_associated_report_detail(result: dict[str, Any]) -> str:
253
277
  data = result.get("data") if isinstance(result.get("data"), dict) else {}
254
278
  selection = data.get("selection") if isinstance(data.get("selection"), dict) else {}
@@ -416,6 +440,121 @@ def _first_present(payload: dict[str, Any], *keys: str) -> Any:
416
440
  return None
417
441
 
418
442
 
443
+ def _task_action_success_label(action: str) -> str:
444
+ return {
445
+ "approve": "已通过",
446
+ "reject": "已驳回",
447
+ "rollback": "已退回",
448
+ "transfer": "已转交",
449
+ "save_only": "已保存",
450
+ "urge": "已催办",
451
+ }.get(action, "已执行")
452
+
453
+
454
+ def _task_action_failure_label(action: str) -> str:
455
+ return {
456
+ "approve": "审批失败",
457
+ "reject": "驳回失败",
458
+ "rollback": "退回失败",
459
+ "transfer": "转交失败",
460
+ "save_only": "保存失败",
461
+ "urge": "催办失败",
462
+ }.get(action, "执行失败")
463
+
464
+
465
+ def _task_action_partial_success_message(result: dict[str, Any]) -> str:
466
+ error_code = str(result.get("error_code") or "").strip().upper()
467
+ if error_code == "WORKFLOW_CONTINUATION_UNVERIFIED":
468
+ return "动作已提交,但暂未完成后续流程验证。可使用 --json 查看详细信息。"
469
+ if error_code == "TASK_ALREADY_PROCESSED":
470
+ return "当前待办已不可操作,系统判断流程可能已被其他人处理。可使用 --json 查看详细信息。"
471
+ warnings = result.get("warnings")
472
+ if isinstance(warnings, list):
473
+ for warning in warnings:
474
+ if not isinstance(warning, dict):
475
+ continue
476
+ code = str(warning.get("code") or "").strip().upper()
477
+ if code == "TASK_ALREADY_PROCESSED_UNCONFIRMED_ACTOR":
478
+ return "当前待办已不可操作,系统判断流程可能已被其他人处理。可使用 --json 查看详细信息。"
479
+ if code == "WORKFLOW_CONTINUATION_UNVERIFIED":
480
+ return "动作已提交,但暂未完成后续流程验证。可使用 --json 查看详细信息。"
481
+ return "动作已提交,但结果验证不完整。可使用 --json 查看详细信息。"
482
+
483
+
484
+ def _task_action_failure_reason(result: dict[str, Any]) -> str | None:
485
+ error_code = str(result.get("error_code") or "").strip().upper()
486
+ mapped_error = {
487
+ "TASK_CONTEXT_VISIBILITY_UNVERIFIED": "当前待办已不可操作,且系统未能确认是否已被处理。",
488
+ "TASK_SAVE_ONLY_VERIFICATION_FAILED": "保存请求已发送,但未能确认字段是否全部保存成功。",
489
+ "WORKFLOW_CONTINUATION_UNVERIFIED": "动作已提交,但暂未验证到流程继续推进。",
490
+ "TASK_ALREADY_PROCESSED": "当前待办已不可操作,系统判断流程可能已被其他人处理。",
491
+ }.get(error_code)
492
+ if mapped_error:
493
+ return mapped_error
494
+
495
+ warnings = result.get("warnings")
496
+ if isinstance(warnings, list):
497
+ for warning in warnings:
498
+ if isinstance(warning, dict) and warning.get("message"):
499
+ return str(warning.get("message"))
500
+
501
+ data = result.get("data") if isinstance(result.get("data"), dict) else {}
502
+ transport_error = data.get("transport_error") if isinstance(data.get("transport_error"), dict) else {}
503
+ backend_code = transport_error.get("backend_code")
504
+ http_status = transport_error.get("http_status")
505
+ if backend_code not in (None, ""):
506
+ return f"后端返回错误码 {backend_code}。"
507
+ if http_status not in (None, ""):
508
+ return f"请求返回 HTTP {http_status}。"
509
+
510
+ verification = result.get("verification") if isinstance(result.get("verification"), dict) else {}
511
+ record_state_error = verification.get("record_state_error") if isinstance(verification.get("record_state_error"), dict) else {}
512
+ backend_code = record_state_error.get("backend_code")
513
+ http_status = record_state_error.get("http_status")
514
+ if backend_code not in (None, ""):
515
+ return f"后端返回错误码 {backend_code}。"
516
+ if http_status not in (None, ""):
517
+ return f"请求返回 HTTP {http_status}。"
518
+ if error_code:
519
+ return f"错误码:{error_code}"
520
+ return None
521
+
522
+
523
+ def _task_action_debug_lines(result: dict[str, Any]) -> list[str]:
524
+ lines: list[str] = []
525
+ error_code = result.get("error_code")
526
+ if error_code not in (None, ""):
527
+ lines.append(f"error_code: {error_code}")
528
+
529
+ data = result.get("data") if isinstance(result.get("data"), dict) else {}
530
+ transport_error = data.get("transport_error") if isinstance(data.get("transport_error"), dict) else {}
531
+ for key in ("backend_code", "http_status", "category"):
532
+ value = transport_error.get(key)
533
+ if value not in (None, ""):
534
+ lines.append(f"{key}: {value}")
535
+
536
+ verification = result.get("verification") if isinstance(result.get("verification"), dict) else {}
537
+ for key in (
538
+ "runtime_continuation_verified",
539
+ "task_context_visibility_verified",
540
+ "fields_saved_verified",
541
+ "task_still_actionable",
542
+ "workflow_not_advanced",
543
+ "record_state_readable",
544
+ ):
545
+ if key in verification and verification.get(key) is not None:
546
+ lines.append(f"{key}: {verification.get(key)}")
547
+
548
+ record_state_error = verification.get("record_state_error") if isinstance(verification.get("record_state_error"), dict) else {}
549
+ for key in ("backend_code", "http_status", "category"):
550
+ value = record_state_error.get(key)
551
+ if value not in (None, ""):
552
+ entry = f"record_state_{key}: {value}"
553
+ if entry not in lines:
554
+ lines.append(entry)
555
+ return lines
556
+
557
+
419
558
  _FORMATTERS = {
420
559
  "auth_whoami": _format_whoami,
421
560
  "workspace_list": _format_workspace_list,
@@ -426,6 +565,7 @@ _FORMATTERS = {
426
565
  "record_list": _format_record_list,
427
566
  "task_list": _format_task_list,
428
567
  "task_get": _format_task_get,
568
+ "task_action_execute": _format_task_action,
429
569
  "task_associated_report_detail_get": _format_task_associated_report_detail,
430
570
  "import_verify": _format_import_verify,
431
571
  "import_status": _format_import_status,
@@ -152,6 +152,7 @@ def _emit_cli_effective_context_notice(args: argparse.Namespace, context: CliCon
152
152
  spec = cli_public_tool_spec_from_namespace(args)
153
153
  if spec is None or not spec.cli_show_effective_context:
154
154
  return
155
+ hide_context_line = bool(getattr(args, "hide_effective_context_line", False))
155
156
  sessions = getattr(context, "sessions", None)
156
157
  if sessions is None or not hasattr(sessions, "get_profile"):
157
158
  return
@@ -168,9 +169,13 @@ def _emit_cli_effective_context_notice(args: argparse.Namespace, context: CliCon
168
169
  workspace_label = f"{workspace_name} ({workspace_id})"
169
170
  else:
170
171
  workspace_label = str(workspace_id)
171
- lines = [f"Context: profile={profile_name} workspace={workspace_label}"]
172
+ lines: list[str] = []
173
+ if not hide_context_line:
174
+ lines.append(f"Context: profile={profile_name} workspace={workspace_label}")
172
175
  if spec.cli_context_write and profile_name == "default":
173
176
  lines.append("Warning: using default profile for a workspace-sensitive write command")
177
+ if not lines:
178
+ return
174
179
  stream.write("\n".join(lines) + "\n")
175
180
 
176
181