@alipay/alipay-aipay 1.6.8-beta3 → 1.6.8-beta5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alipay/alipay-aipay",
3
- "version": "1.6.8-beta3",
3
+ "version": "1.6.8-beta5",
4
4
  "description": "支付宝 AI 付接入 Skill 安装包 - 优先安装 Agent Skill,并在 macOS/Linux 后台准备用户级 CLI 工具",
5
5
  "type": "module",
6
6
  "bin": {
package/skill/VERSION CHANGED
@@ -1 +1 @@
1
- 1.6.8-beta3
1
+ 1.6.8-beta5
@@ -315,6 +315,7 @@ function dynamicMccRecordsFromCatalog(rawCatalog) {
315
315
  throw new Error('mcc-catalog-json 必须是非空数组');
316
316
  }
317
317
  const records = new Map();
318
+ const refs = new Set();
318
319
  for (const item of catalog) {
319
320
  if (!item || typeof item !== 'object' || Array.isArray(item)) throw new Error('mcc-catalog-json 项必须是对象');
320
321
  const code = String(item.mccCode || item.code || '').toUpperCase();
@@ -325,6 +326,10 @@ function dynamicMccRecordsFromCatalog(rawCatalog) {
325
326
  if (typeof item.needSpecialQual !== 'boolean') throw new Error(`动态 MCC ${code} 缺少布尔型 needSpecialQual`);
326
327
  if (!Array.isArray(item.qualificationGroups)) throw new Error(`动态 MCC ${code} 的 qualificationGroups 必须是数组`);
327
328
  if (records.has(code)) throw new Error(`动态 MCC 重复:${code}`);
329
+ const ref = item.mccRef == null ? '' : String(item.mccRef);
330
+ if (ref && !/^mcc-\d+$/u.test(ref)) throw new Error(`动态 MCC ${code} 的 mccRef 非法`);
331
+ if (ref && refs.has(ref)) throw new Error(`动态 MCC 目录引用重复:${ref}`);
332
+ if (ref) refs.add(ref);
328
333
  const needSpecialQual = item.needSpecialQual;
329
334
  const qualificationGroups = item.qualificationGroups;
330
335
  records.set(code, {
@@ -334,6 +339,7 @@ function dynamicMccRecordsFromCatalog(rawCatalog) {
334
339
  needSpecialQual,
335
340
  qualificationGroups,
336
341
  qualification: mccQualificationSummary({ needSpecialQual, qualificationGroups }),
342
+ ref: ref || undefined,
337
343
  dynamic: true
338
344
  });
339
345
  }
@@ -352,9 +358,20 @@ function sortedMccRecords(records) {
352
358
  return [...records.values()].sort((left, right) => left.code.localeCompare(right.code));
353
359
  }
354
360
 
361
+ function mccAgentRefEntries(records) {
362
+ const refs = new Map();
363
+ sortedMccRecords(records).forEach((record, index) => {
364
+ const ref = record.ref || `mcc-${index + 1}`;
365
+ if (refs.has(ref)) throw new Error(`动态 MCC 目录引用重复:${ref}`);
366
+ refs.set(ref, record.code);
367
+ });
368
+ return refs;
369
+ }
370
+
355
371
  function mccCatalogForAgent(records) {
356
- return sortedMccRecords(records).map((record, index) => ({
357
- ref: `mcc-${index + 1}`,
372
+ const refsByCode = new Map([...mccAgentRefEntries(records)].map(([ref, code]) => [code, ref]));
373
+ return sortedMccRecords(records).map((record) => ({
374
+ ref: refsByCode.get(record.code),
358
375
  mccName: record.name,
359
376
  mccDescription: record.applicableBusinesses
360
377
  }));
@@ -362,7 +379,7 @@ function mccCatalogForAgent(records) {
362
379
 
363
380
  function parseAgentCandidateRefs(args, records) {
364
381
  const raw = optional(args, 'agent-candidate-refs-json');
365
- if (raw === undefined) return { provided: false, codes: [] };
382
+ if (raw === undefined) return { codes: [] };
366
383
  let parsed;
367
384
  try {
368
385
  parsed = JSON.parse(raw);
@@ -372,7 +389,7 @@ function parseAgentCandidateRefs(args, records) {
372
389
  if (!Array.isArray(parsed) || parsed.length > 8 || parsed.some((ref) => typeof ref !== 'string')) {
373
390
  throw new Error('agent-candidate-refs-json 必须是最多 8 项的 JSON 字符串数组');
374
391
  }
375
- const refs = new Map(sortedMccRecords(records).map((record, index) => [`mcc-${index + 1}`, record.code]));
392
+ const refs = mccAgentRefEntries(records);
376
393
  const codes = [];
377
394
  for (const ref of parsed) {
378
395
  if (!refs.has(ref) || codes.includes(refs.get(ref))) {
@@ -380,7 +397,7 @@ function parseAgentCandidateRefs(args, records) {
380
397
  }
381
398
  codes.push(refs.get(ref));
382
399
  }
383
- return { provided: true, codes };
400
+ return { codes };
384
401
  }
385
402
 
386
403
  function mccTableSummary(records) {
@@ -13,7 +13,7 @@
13
13
 
14
14
  `queryMcc`、签约查询和签约提交使用 `{"request":{...},"ctx":{}}`;`batchQueryMcc` 使用 `{"request":{"mccCodes":[...]}}`。完整 payload 只在脚本中维护;Agent 不从本模块自行构造 MCP 调用。
15
15
 
16
- `queryMcc` 成功树按同一 run、产品短时缓存,并逐项提供二级 MCC 编码、名称、描述和布尔型 `needSpecialQual`;任一必需字段缺失、为空或类型异常均失败关闭,不从其他字段回退。非精确编码时,Agent 应先理解用户原话,并在行业俗称或场景表达与目录用词可能不同时于首次调用一并提供有原话依据的行业概念或同义表达;本地词面结果为 `NONE`、候选冲突或低置信时,runner 可发出一次目录复核 marker,携带同一次 `queryMcc` 返回的当前产品 MCC 名称、描述和稳定引用;Agent 只返回该 marker 中的目录引用,runner 映射并校验为当前动态目录中的候选,不接受自由提交 MCC code。目录复核不新增 MCP/网络调用,语义扩充仍按 flow 最多完成两次。候选确定后,仅把其中 `needSpecialQual=true` 的编码集中交给一次 `batchQueryMcc` 获取特殊资质项;没有此类候选时不调用。多候选的完整树事实和资格详情按 run、产品、原查询、规范化语义词和选择回执缓存,用户选择时携带同一语义词且有效命中则两种 MCC 方法都不重调。缓存默认 1800 秒、无有效 run 不落盘;过期、损坏、符号链接、跨 run、语义词变化或事实哈希不一致时忽略并从完整当前产品目录重查,不修补缓存事实,不做产品级全量预取。
16
+ `queryMcc` 成功树按同一 run、产品短时缓存,并逐项提供二级 MCC 编码、名称、描述和布尔型 `needSpecialQual`;任一必需字段缺失、为空或类型异常均失败关闭,不从其他字段回退。非精确编码时,Agent 应先理解用户原话,并在行业俗称或场景表达与目录用词可能不同时于首次调用一并提供有原话依据的行业概念或同义表达;本地词面结果为 `NONE`、候选冲突或低置信时,runner 可发出一次目录复核 marker,携带同一次 `queryMcc` 返回的当前产品 MCC 名称、描述和稳定引用;Agent 只返回该 marker 中的目录引用,runner 映射并校验为当前动态目录中的候选,不接受自由提交 MCC code。稳定引用由完整动态目录生成,并随候选裁剪和资格详情合并原样携带;最终方案 runner 不得按缩减后的候选数组重新编号。目录复核不新增 MCP/网络调用,语义扩充仍按 flow 最多完成两次。候选确定后,仅把其中 `needSpecialQual=true` 的编码集中交给一次 `batchQueryMcc` 获取特殊资质项;没有此类候选时不调用。多候选的完整树事实和资格详情按 run、产品、原查询、规范化语义词和选择回执缓存,用户选择时携带同一语义词且有效命中则两种 MCC 方法都不重调。缓存默认 1800 秒、无有效 run 不落盘;过期、损坏、符号链接、跨 run、语义词变化或事实哈希不一致时忽略并从完整当前产品目录重查,不修补缓存事实,不做产品级全量预取。
17
17
 
18
18
  ## 签约状态
19
19
 
@@ -323,6 +323,92 @@ mcc_semantic_retry_file() {
323
323
  "$(mcc_cache_component "$sales_code")"
324
324
  }
325
325
 
326
+ mcc_agent_review_file() {
327
+ local product_type="$1" sales_code="$2" root scope
328
+ root="$(mcc_cache_root)" || return 1
329
+ scope="$(mcc_cache_scope_id)" || return 1
330
+ if [ -e "$root" ] && { [ ! -d "$root" ] || [ -L "$root" ]; }; then
331
+ return 1
332
+ fi
333
+ mkdir -p "$root" 2>/dev/null || return 1
334
+ chmod 700 "$root" 2>/dev/null || true
335
+ printf '%s/%s.%s.%s.agent-reviews.json' \
336
+ "$root" \
337
+ "$(mcc_cache_component "$scope")" \
338
+ "$(mcc_cache_component "$product_type")" \
339
+ "$(mcc_cache_component "$sales_code")"
340
+ }
341
+
342
+ mcc_agent_review_issued() {
343
+ local product_type="$1" sales_code="$2" input_id="$3" file
344
+ file="$(mcc_agent_review_file "$product_type" "$sales_code")" || return 1
345
+ [ -f "$file" ] && [ ! -L "$file" ] || return 1
346
+ jq -e \
347
+ --arg inputId "$input_id" \
348
+ '.schemaVersion == 1
349
+ and ((.entries // null) | type == "array")
350
+ and all(.entries[];
351
+ type == "object"
352
+ and ((.query // null) | type == "string")
353
+ and ((.inputId // null) | type == "string")
354
+ )
355
+ and any(.entries[]; .inputId == $inputId)' \
356
+ "$file" >/dev/null 2>&1
357
+ }
358
+
359
+ mark_mcc_agent_review_issued() {
360
+ local product_type="$1" sales_code="$2" query="$3" input_id="$4"
361
+ local file dir lock tmp
362
+ file="$(mcc_agent_review_file "$product_type" "$sales_code")" || return 1
363
+ dir="${file%/*}"
364
+ lock="${file}.lock"
365
+ if ! mkdir "$lock" 2>/dev/null; then
366
+ return 1
367
+ fi
368
+ if [ -e "$file" ] && { [ ! -f "$file" ] || [ -L "$file" ]; }; then
369
+ rmdir "$lock" 2>/dev/null || true
370
+ return 1
371
+ fi
372
+ tmp="$(mktemp "$dir/.mcc-agent-reviews.XXXXXX" 2>/dev/null)" || {
373
+ rmdir "$lock" 2>/dev/null || true
374
+ return 1
375
+ }
376
+ chmod 600 "$tmp" 2>/dev/null || true
377
+ if [ -f "$file" ]; then
378
+ if ! jq -ce \
379
+ --arg query "$query" \
380
+ --arg inputId "$input_id" '
381
+ if .schemaVersion != 1 or ((.entries // null) | type != "array") then
382
+ error("invalid MCC agent review state")
383
+ elif any(.entries[]; .query == $query and .inputId == $inputId) then
384
+ .
385
+ else
386
+ .entries += [{query: $query, inputId: $inputId}]
387
+ end
388
+ ' "$file" >"$tmp" 2>/dev/null; then
389
+ rm -f "$tmp" 2>/dev/null || true
390
+ rmdir "$lock" 2>/dev/null || true
391
+ return 1
392
+ fi
393
+ else
394
+ if ! jq -cn \
395
+ --arg query "$query" \
396
+ --arg inputId "$input_id" \
397
+ '{schemaVersion: 1, entries: [{query: $query, inputId: $inputId}]}' \
398
+ >"$tmp" 2>/dev/null; then
399
+ rm -f "$tmp" 2>/dev/null || true
400
+ rmdir "$lock" 2>/dev/null || true
401
+ return 1
402
+ fi
403
+ fi
404
+ if ! mv "$tmp" "$file" 2>/dev/null; then
405
+ rm -f "$tmp" 2>/dev/null || true
406
+ rmdir "$lock" 2>/dev/null || true
407
+ return 1
408
+ fi
409
+ rmdir "$lock" 2>/dev/null || true
410
+ }
411
+
326
412
  claim_mcc_semantic_retry() {
327
413
  local product_type="$1" sales_code="$2" query="$3" input_id="$4" semantic_terms_json="$5"
328
414
  local file dir lock entry original_query retry_count next_retry_count now tmp lock_acquired=false
@@ -588,7 +674,10 @@ mcc_rows_from_tree() {
588
674
  elif (($rows | map(.fullCode) | unique | length) != ($rows | length)) then
589
675
  error("queryMcc returned duplicate selectable mcc")
590
676
  else
591
- $rows
677
+ ($rows
678
+ | sort_by(.fullCode)
679
+ | to_entries
680
+ | map(.value + {mccRef: ("mcc-" + ((.key + 1) | tostring))}))
592
681
  end
593
682
  ' 2>/dev/null
594
683
  }
@@ -596,16 +685,17 @@ mcc_rows_from_tree() {
596
685
  normalize_mcc_tree_catalog() {
597
686
  local rows_json="$1"
598
687
  jq -cn --argjson rows "$rows_json" '
599
- [
600
- $rows[] |
601
- {
688
+ ($rows |
689
+ map({
602
690
  mccCode: .fullCode,
603
691
  mccName: .treeName,
604
692
  mccDescription: .treeDescription,
605
693
  needSpecialQual: .treeNeedSpecialQual,
606
694
  qualificationGroups: []
607
- }
608
- ]
695
+ }) |
696
+ sort_by(.mccCode) |
697
+ to_entries |
698
+ map(.value + {mccRef: ("mcc-" + ((.key + 1) | tostring))}))
609
699
  ' 2>/dev/null
610
700
  }
611
701
 
@@ -654,6 +744,15 @@ query_mcc_detail_chunk() {
654
744
  printf '%s' "$business"
655
745
  }
656
746
 
747
+ mcc_match_error_kind() {
748
+ case "$1" in
749
+ *agent-candidate-refs-json*) printf 'INVALID_AGENT_CANDIDATE_REFS' ;;
750
+ *semantic-terms-json*) printf 'INVALID_SEMANTIC_TERMS' ;;
751
+ *mcc-catalog-json*) printf 'INVALID_DYNAMIC_CATALOG' ;;
752
+ *) printf 'MATCHER_RUNTIME_ERROR' ;;
753
+ esac
754
+ }
755
+
657
756
  normalize_mcc_catalog() {
658
757
  local rows_json="$1" details_json="$2"
659
758
  jq -cn --argjson rows "$rows_json" --argjson details "$details_json" '
@@ -683,6 +782,7 @@ normalize_mcc_catalog() {
683
782
  mccName: $row.treeName,
684
783
  mccDescription: $row.treeDescription,
685
784
  needSpecialQual: $row.treeNeedSpecialQual,
785
+ mccRef: $row.mccRef,
686
786
  qualificationGroups: (if $row.treeNeedSpecialQual then
687
787
  (($detail.qualificationGroups // []) | map({
688
788
  qualifications: ((.qualifications // []) | map({
@@ -713,6 +813,7 @@ normalize_mcc_catalog() {
713
813
 
714
814
  run_mcc_match_codes() {
715
815
  local query="$1" product_type="$2" root_intent="$3" catalog="$4" semantic_terms_json="$5" agent_candidate_refs_json="${6:-}" runner_out runner_err runner_rc line resolution=""
816
+ local runner_error error_kind
716
817
  runner_out=$(mktemp "${TMPDIR:-/tmp}/mcc_match_out.XXXXXX") || { echo "❌ 创建临时文件失败"; exit 1; }
717
818
  runner_err=$(mktemp "${TMPDIR:-/tmp}/mcc_match_err.XXXXXX") || { rm -f "$runner_out"; echo "❌ 创建临时文件失败"; exit 1; }
718
819
  local node_args=(
@@ -729,7 +830,16 @@ run_mcc_match_codes() {
729
830
  "${NODE_BINARY:-node}" "${node_args[@]}" >"$runner_out" 2>"$runner_err"
730
831
  runner_rc=$?
731
832
  if [ "$runner_rc" -ne 0 ] || [ -s "$runner_out" ]; then
833
+ runner_error="$(sed -n 's/^ONBOARDING_MESSAGE_ERROR://p' "$runner_err" | sed -n '1p')"
834
+ error_kind="$(mcc_match_error_kind "$runner_error")"
835
+ if [ "$error_kind" = "INVALID_AGENT_CANDIDATE_REFS" ] && [ -n "$agent_candidate_refs_json" ]; then
836
+ rm -f "$runner_out" "$runner_err"
837
+ emit_control "MCC_AGENT_CANDIDATE_REFS_IGNORED=INVALID"
838
+ run_mcc_match_codes "$query" "$product_type" "$root_intent" "$catalog" "$semantic_terms_json"
839
+ return $?
840
+ fi
732
841
  rm -f "$runner_out" "$runner_err"
842
+ emit_control "MCC_MATCH_ERROR=$error_kind"
733
843
  mcc_query_failed
734
844
  fi
735
845
  while IFS= read -r line; do
@@ -754,7 +864,7 @@ validate_mcc_selection_cache() {
754
864
  }
755
865
 
756
866
  run_resolve_plan() {
757
- local query="" product_type="" root_intent="" semantic_terms_json='[]' agent_candidate_refs_json="" select_value="" selection_receipt="" input_id="" sales_code tree rows tree_catalog resolution match_type semantic_status full_codes candidate_rows qualification_rows details catalog selection_cache="" used_selection_cache=false stale_selection=false resolved_semantic_terms='[]' retry_input_id="" retry_rc=0 next_retry_count=0 retry_reason="" retry_payload="" agent_review_required="false" agent_review_payload=""
867
+ local query="" product_type="" root_intent="" semantic_terms_json='[]' agent_candidate_refs_json="" select_value="" selection_receipt="" input_id="" sales_code tree rows tree_catalog resolution match_type semantic_status full_codes candidate_rows qualification_rows details catalog selection_cache="" used_selection_cache=false stale_selection=false resolved_semantic_terms='[]' retry_rc=0 next_retry_count=0 retry_reason="" retry_payload="" agent_review_required="false" agent_review_payload=""
758
868
  shift
759
869
  while [[ $# -gt 0 ]]; do
760
870
  case "$1" in
@@ -840,20 +950,25 @@ run_resolve_plan() {
840
950
  semantic_status=$(printf '%s' "$resolution" | jq -r '.semanticStatus // ""' 2>/dev/null) || mcc_query_failed
841
951
  resolved_semantic_terms=$(printf '%s' "$resolution" | jq -c '.semanticTerms // []' 2>/dev/null) || mcc_query_failed
842
952
  agent_review_required=$(printf '%s' "$resolution" | jq -r '.agentReview.required // false' 2>/dev/null) || mcc_query_failed
953
+ if [ -n "$input_id" ] && [ -n "$agent_candidate_refs_json" ]; then
954
+ mark_mcc_agent_review_issued "$product_type" "$sales_code" "$query" "$input_id" || true
955
+ fi
843
956
  if [ "$agent_review_required" = "true" ] && [ -z "$agent_candidate_refs_json" ] && [ -n "$input_id" ]; then
844
957
  agent_review_payload=$(printf '%s' "$resolution" | jq -c '.agentReview' 2>/dev/null) || mcc_query_failed
845
- emit_control "MCC_QUERY_RESULT=SUCCESS"
846
- emit_control "MCC_RESOLUTION=$(printf '%s' "$resolution" | jq -c 'del(.agentReview)' 2>/dev/null)"
847
- emit_control "MCC_AGENT_REVIEW_REQUIRED=$agent_review_payload"
848
- return 0
958
+ if ! mcc_agent_review_issued "$product_type" "$sales_code" "$input_id"; then
959
+ mark_mcc_agent_review_issued "$product_type" "$sales_code" "$query" "$input_id" || true
960
+ emit_control "MCC_QUERY_RESULT=SUCCESS"
961
+ emit_control "MCC_RESOLUTION=$(printf '%s' "$resolution" | jq -c 'del(.agentReview)' 2>/dev/null)"
962
+ emit_control "MCC_AGENT_REVIEW_REQUIRED=$agent_review_payload"
963
+ return 0
964
+ fi
849
965
  fi
850
966
  if [ "$match_type" = "NONE" ] \
851
967
  && [ "$semantic_status" != "DYNAMIC_CATALOG_CODE_NOT_FOUND" ] \
852
968
  && [ "$semantic_status" != "DYNAMIC_CATALOG_CODE_AMBIGUOUS" ]; then
853
969
  if [ -n "$input_id" ]; then
854
- retry_input_id="$input_id"
855
970
  next_retry_count="$(claim_mcc_semantic_retry \
856
- "$product_type" "$sales_code" "$query" "$retry_input_id" "$resolved_semantic_terms" 2>/dev/null)"
971
+ "$product_type" "$sales_code" "$query" "$input_id" "$resolved_semantic_terms" 2>/dev/null)"
857
972
  retry_rc=$?
858
973
  else
859
974
  # A retry without a message identity cannot be bound to one user input.
@@ -884,7 +999,7 @@ run_resolve_plan() {
884
999
  else
885
1000
  full_codes=$(printf '%s' "$resolution" | jq -c '.mergedCandidateCodes // []' 2>/dev/null) || mcc_query_failed
886
1001
  fi
887
- if [ -z "${catalog:-}" ]; then
1002
+ if [ -z "${catalog:-}" ]; then
888
1003
  if ! printf '%s' "$full_codes" | jq -e 'type == "array" and length > 0' >/dev/null 2>&1; then
889
1004
  catalog="$tree_catalog"
890
1005
  else
@@ -904,6 +1019,15 @@ run_resolve_plan() {
904
1019
  fi
905
1020
  fi
906
1021
  fi
1022
+ if [ -n "$agent_candidate_refs_json" ] && ! printf '%s' "$catalog" | jq -e \
1023
+ --argjson refs "$agent_candidate_refs_json" '
1024
+ ([.[] | .mccRef?] | map(select(type == "string"))) as $available |
1025
+ ($refs | type == "array" and all(.[]; . as $ref | $available | index($ref) != null))
1026
+ ' >/dev/null 2>&1; then
1027
+ # An invalid Agent review may have already fallen back to local matches.
1028
+ # Do not pass that invalid reference into the reduced final catalog.
1029
+ agent_candidate_refs_json=""
1030
+ fi
907
1031
  node_args=(
908
1032
  "$ONBOARDING_RUNNER" mcc-resolve-plan
909
1033
  --query "$query"
@@ -955,7 +1079,7 @@ case "${1:-}" in
955
1079
  run_resolve_plan "$@"
956
1080
  ;;
957
1081
  *)
958
- echo "用法: bash mcc.sh resolve-plan --query <经营描述|MCC编码> --product-type <aipay|webpay|apppay> --root-intent <onboarding_only|full_process> [--semantic-terms-json <JSON数组>] [--select <MCC编码> --selection-receipt <receipt>] [--input-id <USER_INPUT_ID>]"
1082
+ echo "用法: bash mcc.sh resolve-plan --query <经营描述|MCC编码> --product-type <aipay|webpay|apppay> --root-intent <onboarding_only|full_process> [--semantic-terms-json <JSON数组>] [--agent-candidate-refs-json <JSON数组>] [--select <MCC编码> --selection-receipt <receipt>] [--input-id <USER_INPUT_ID>]"
959
1083
  exit 1
960
1084
  ;;
961
1085
  esac