@alipay/alipay-aipay 1.8.2 → 1.8.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.
Files changed (29) hide show
  1. package/package.json +3 -3
  2. package/skill/VERSION +1 -1
  3. package/skill/references/integration/flow.md +27 -30
  4. package/skill/references/integration/modules/checklist.md +4 -6
  5. package/skill/references/integration/modules/sandbox/sandbox-setup-guide.md +2 -2
  6. package/skill/references/normal/integration-worker.md +1 -1
  7. package/skill/references/normal/json/customer-messages.json +1 -1
  8. package/skill/references/normal/scripts/flow_loader.mjs +9 -0
  9. package/skill/references/normal/scripts/integration_message_runner.mjs +81 -14
  10. package/skill/references/normal/scripts/onboarding_message_runner.mjs +80 -250
  11. package/skill/references/normal/scripts/render_customer_message.mjs +6 -6
  12. package/skill/references/onboarding/flow.md +9 -9
  13. package/skill/references/onboarding/modules/product-sign.md +1 -1
  14. package/skill/references/onboarding/modules/scripts/mcc.sh +151 -290
  15. package/skill/references/runtime-entry.md +2 -2
  16. package/skill-pre/VERSION +1 -1
  17. package/skill-pre/references/integration/flow.md +27 -30
  18. package/skill-pre/references/integration/modules/checklist.md +4 -6
  19. package/skill-pre/references/integration/modules/sandbox/sandbox-setup-guide.md +2 -2
  20. package/skill-pre/references/normal/integration-worker.md +1 -1
  21. package/skill-pre/references/normal/json/customer-messages.json +1 -1
  22. package/skill-pre/references/normal/scripts/flow_loader.mjs +9 -0
  23. package/skill-pre/references/normal/scripts/integration_message_runner.mjs +81 -14
  24. package/skill-pre/references/normal/scripts/onboarding_message_runner.mjs +80 -250
  25. package/skill-pre/references/normal/scripts/render_customer_message.mjs +6 -6
  26. package/skill-pre/references/onboarding/flow.md +9 -9
  27. package/skill-pre/references/onboarding/modules/product-sign.md +1 -1
  28. package/skill-pre/references/onboarding/modules/scripts/mcc.sh +151 -290
  29. package/skill-pre/references/runtime-entry.md +2 -2
@@ -4,7 +4,7 @@
4
4
  # 功能描述: 查询当前产品支持的 MCC 经营类目并托管 MCC 匹配与方案展示
5
5
  # 调用位置: Step 1 方案规划
6
6
  # 用法:
7
- # 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>]
7
+ # bash mcc.sh resolve-plan --query <经营描述|MCC编码> --product-type <aipay|webpay|apppay> --root-intent <onboarding_only|full_process> [--agent-candidate-refs-json <JSON数组>] [--select <MCC编码> --selection-receipt <receipt>] [--input-id <USER_INPUT_ID>]
8
8
  #=============================================================================
9
9
 
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -20,7 +20,6 @@ fi
20
20
  source "${SCRIPT_DIR}/network_retry.sh"
21
21
 
22
22
  ONBOARDING_RUNNER="${SCRIPT_DIR}/../../../normal/scripts/onboarding_message_runner.mjs"
23
- MCC_SEMANTIC_RETRY_LIMIT=2
24
23
 
25
24
  require_command jq || exit 1
26
25
  require_command node || exit 1
@@ -62,7 +61,7 @@ network_failure() {
62
61
  }
63
62
 
64
63
  mcc_query_failed() {
65
- echo "❌ MCC 经营类目查询结果暂时无法处理,请稍后重试。"
64
+ echo "经营类目匹配暂时未完成,本次未继续授权或签约。请重新发送一次经营描述,我会重新匹配;如果仍未完成,请稍后再试。"
66
65
  emit_control "MCC_QUERY_RESULT=FAILED"
67
66
  exit 1
68
67
  }
@@ -230,14 +229,11 @@ read_mcc_selection_cache() {
230
229
  --arg receipt "$receipt" \
231
230
  --argjson now "$now" \
232
231
  --argjson ttl "$ttl" '
233
- if .schemaVersion == 3
232
+ if .schemaVersion == 4
234
233
  and .productType == $productType
235
234
  and .salesCode == $salesCode
236
235
  and .query == $query
237
236
  and .selectionReceipt == $receipt
238
- and ((.semanticTerms // null) | type == "array")
239
- and ((.semanticTerms | length) <= 12)
240
- and all(.semanticTerms[]; type == "string")
241
237
  and ((.createdAtEpoch // null) | type == "number")
242
238
  and (($now - .createdAtEpoch) >= 0)
243
239
  and (($now - .createdAtEpoch) <= $ttl)
@@ -271,7 +267,7 @@ read_mcc_selection_cache() {
271
267
  }
272
268
 
273
269
  write_mcc_selection_cache() {
274
- local product_type="$1" sales_code="$2" query="$3" receipt="$4" semantic_terms="$5" candidate_rows="$6" details="$7"
270
+ local product_type="$1" sales_code="$2" query="$3" receipt="$4" candidate_rows="$5" details="$6"
275
271
  local file dir now tmp
276
272
  file="$(mcc_selection_cache_file "$product_type" "$sales_code" "$receipt")" || return 0
277
273
  dir="${file%/*}"
@@ -283,17 +279,15 @@ write_mcc_selection_cache() {
283
279
  --arg salesCode "$sales_code" \
284
280
  --arg query "$query" \
285
281
  --arg selectionReceipt "$receipt" \
286
- --argjson semanticTerms "$semantic_terms" \
287
282
  --argjson createdAtEpoch "$now" \
288
283
  --argjson candidateRows "$candidate_rows" \
289
284
  --argjson details "$details" \
290
285
  '{
291
- schemaVersion: 3,
286
+ schemaVersion: 4,
292
287
  productType: $productType,
293
288
  salesCode: $salesCode,
294
289
  query: $query,
295
290
  selectionReceipt: $selectionReceipt,
296
- semanticTerms: $semanticTerms,
297
291
  createdAtEpoch: $createdAtEpoch,
298
292
  candidateRows: $candidateRows,
299
293
  details: $details
@@ -307,22 +301,6 @@ write_mcc_selection_cache() {
307
301
  }
308
302
  }
309
303
 
310
- mcc_semantic_retry_file() {
311
- local product_type="$1" sales_code="$2" root scope
312
- root="$(mcc_cache_root)" || return 1
313
- scope="$(mcc_cache_scope_id)" || return 1
314
- if [ -e "$root" ] && { [ ! -d "$root" ] || [ -L "$root" ]; }; then
315
- return 1
316
- fi
317
- mkdir -p "$root" 2>/dev/null || return 1
318
- chmod 700 "$root" 2>/dev/null || true
319
- printf '%s/%s.%s.%s.semantic-retries.json' \
320
- "$root" \
321
- "$(mcc_cache_component "$scope")" \
322
- "$(mcc_cache_component "$product_type")" \
323
- "$(mcc_cache_component "$sales_code")"
324
- }
325
-
326
304
  mcc_agent_review_file() {
327
305
  local product_type="$1" sales_code="$2" root scope
328
306
  root="$(mcc_cache_root)" || return 1
@@ -339,32 +317,32 @@ mcc_agent_review_file() {
339
317
  "$(mcc_cache_component "$sales_code")"
340
318
  }
341
319
 
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
320
+ mcc_catalog_digest() {
321
+ printf '%s' "$1" | "${NODE_BINARY:-node}" -e '
322
+ const crypto = require("node:crypto");
323
+ const fs = require("node:fs");
324
+ process.stdout.write(crypto.createHash("sha256").update(fs.readFileSync(0)).digest("hex"));
325
+ ' 2>/dev/null
357
326
  }
358
327
 
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
328
+ claim_mcc_agent_phase() {
329
+ local product_type="$1" sales_code="$2" query="$3" input_id="$4" phase="$5" catalog_digest="$6"
330
+ local file dir lock tmp lock_acquired=false
331
+ case "$phase" in review|repair) ;; *) return 1 ;; esac
332
+ [ -n "$input_id" ] || return 1
333
+ case "$catalog_digest" in ''|*[!0-9a-f]*) return 1 ;; esac
334
+ [ "${#catalog_digest}" -eq 64 ] || return 1
362
335
  file="$(mcc_agent_review_file "$product_type" "$sales_code")" || return 1
363
336
  dir="${file%/*}"
364
337
  lock="${file}.lock"
365
- if ! mkdir "$lock" 2>/dev/null; then
366
- return 1
367
- fi
338
+ for _ in 1 2 3 4 5 6 7 8 9 10; do
339
+ if mkdir "$lock" 2>/dev/null; then
340
+ lock_acquired=true
341
+ break
342
+ fi
343
+ sleep 0.05
344
+ done
345
+ [ "$lock_acquired" = true ] || return 1
368
346
  if [ -e "$file" ] && { [ ! -f "$file" ] || [ -L "$file" ]; }; then
369
347
  rmdir "$lock" 2>/dev/null || true
370
348
  return 1
@@ -377,13 +355,22 @@ mark_mcc_agent_review_issued() {
377
355
  if [ -f "$file" ]; then
378
356
  if ! jq -ce \
379
357
  --arg query "$query" \
380
- --arg inputId "$input_id" '
381
- if .schemaVersion != 1 or ((.entries // null) | type != "array") then
358
+ --arg inputId "$input_id" \
359
+ --arg phase "$phase" \
360
+ --arg catalogDigest "$catalog_digest" '
361
+ if .schemaVersion != 3 or ((.entries // null) | type != "array") then
382
362
  error("invalid MCC agent review state")
383
- elif any(.entries[]; .query == $query and .inputId == $inputId) then
384
- .
363
+ elif $phase == "review" and any(.entries[]; .inputId == $inputId) then
364
+ error("MCC agent review already started")
365
+ elif $phase == "repair" and (
366
+ (any(.entries[]; .query == $query and .inputId == $inputId and .phase == "review" and .catalogDigest == $catalogDigest) | not)
367
+ or any(.entries[]; .inputId == $inputId and .query != $query)
368
+ or any(.entries[]; .query == $query and .inputId == $inputId and .catalogDigest != $catalogDigest)
369
+ or any(.entries[]; .query == $query and .inputId == $inputId and (.phase == "repair" or .phase == "result"))
370
+ ) then
371
+ error("MCC agent repair is not allowed")
385
372
  else
386
- .entries += [{query: $query, inputId: $inputId}]
373
+ .entries += [{query: $query, inputId: $inputId, phase: $phase, catalogDigest: $catalogDigest}]
387
374
  end
388
375
  ' "$file" >"$tmp" 2>/dev/null; then
389
376
  rm -f "$tmp" 2>/dev/null || true
@@ -394,7 +381,9 @@ mark_mcc_agent_review_issued() {
394
381
  if ! jq -cn \
395
382
  --arg query "$query" \
396
383
  --arg inputId "$input_id" \
397
- '{schemaVersion: 1, entries: [{query: $query, inputId: $inputId}]}' \
384
+ --arg phase "$phase" \
385
+ --arg catalogDigest "$catalog_digest" \
386
+ '{schemaVersion: 3, entries: [{query: $query, inputId: $inputId, phase: $phase, catalogDigest: $catalogDigest}]}' \
398
387
  >"$tmp" 2>/dev/null; then
399
388
  rm -f "$tmp" 2>/dev/null || true
400
389
  rmdir "$lock" 2>/dev/null || true
@@ -407,21 +396,18 @@ mark_mcc_agent_review_issued() {
407
396
  return 1
408
397
  fi
409
398
  rmdir "$lock" 2>/dev/null || true
399
+ return 0
410
400
  }
411
401
 
412
- claim_mcc_semantic_retry() {
413
- local product_type="$1" sales_code="$2" query="$3" input_id="$4" semantic_terms_json="$5"
414
- local file dir lock entry original_query retry_count next_retry_count now tmp lock_acquired=false
415
- local current_terms previous_terms missing_previous_terms new_terms
416
- file="$(mcc_semantic_retry_file "$product_type" "$sales_code")" || return 2
402
+ bind_mcc_agent_result() {
403
+ local product_type="$1" sales_code="$2" query="$3" input_id="$4" candidate_codes="$5" catalog_digest="$6"
404
+ local file dir lock tmp lock_acquired=false
405
+ [ -n "$input_id" ] || return 1
406
+ case "$catalog_digest" in ''|*[!0-9a-f]*) return 1 ;; esac
407
+ [ "${#catalog_digest}" -eq 64 ] || return 1
408
+ file="$(mcc_agent_review_file "$product_type" "$sales_code")" || return 1
417
409
  dir="${file%/*}"
418
410
  lock="${file}.lock"
419
- current_terms="$(printf '%s' "$semantic_terms_json" | jq -ce '
420
- select(type == "array" and all(.[]; type == "string")) | unique | sort
421
- ' 2>/dev/null)" || return 2
422
-
423
- # Claim and persist one retry under a bounded private lock. Lock contention
424
- # is treated as exhausted so a broken local state cannot cause a retry loop.
425
411
  for _ in 1 2 3 4 5 6 7 8 9 10; do
426
412
  if mkdir "$lock" 2>/dev/null; then
427
413
  lock_acquired=true
@@ -429,165 +415,55 @@ claim_mcc_semantic_retry() {
429
415
  fi
430
416
  sleep 0.05
431
417
  done
432
- [ "$lock_acquired" = true ] || return 2
433
-
434
- if [ -e "$file" ]; then
435
- if [ ! -f "$file" ] || [ -L "$file" ]; then
436
- rmdir "$lock" 2>/dev/null || true
437
- return 2
438
- fi
439
- now="$(date +%s 2>/dev/null)" || {
440
- rmdir "$lock" 2>/dev/null || true
441
- return 2
442
- }
443
- entry="$(jq -cr \
444
- --arg inputId "$input_id" \
445
- '
446
- if .schemaVersion == 1
447
- and ((.entries // null) | type == "array")
448
- and all(.entries[];
449
- type == "object"
450
- and ((.inputId // null) | type == "string")
451
- and ((.query // null) | type == "string")
452
- and ((.count // null) | type == "number")
453
- and (.count >= 0)
454
- and ((.count | floor) == .count)
455
- and ((.updatedAtEpoch // null) | type == "number")
456
- and ((.semanticTerms // []) | type == "array")
457
- and all((.semanticTerms // [])[]; type == "string")
458
- )
459
- then
460
- ([.entries[] |
461
- select(
462
- .inputId == $inputId
463
- ) |
464
- {query, count, updatedAtEpoch, semanticTerms: (.semanticTerms // [])}
465
- ] | sort_by(.updatedAtEpoch) | last) // null
466
- else
467
- error("invalid MCC semantic retry state")
468
- end
469
- ' "$file" 2>/dev/null)" || {
470
- rmdir "$lock" 2>/dev/null || true
471
- return 2
472
- }
473
- else
474
- entry='null'
475
- fi
476
-
477
- if [ "$entry" = "null" ]; then
478
- original_query="$query"
479
- retry_count=0
480
- previous_terms='[]'
481
- else
482
- original_query="$(printf '%s' "$entry" | jq -r '.query // empty' 2>/dev/null)" || original_query=""
483
- retry_count="$(printf '%s' "$entry" | jq -r '.count // -1' 2>/dev/null)" || retry_count=-1
484
- previous_terms="$(printf '%s' "$entry" | jq -c '.semanticTerms // []' 2>/dev/null)" || previous_terms=""
485
- if [ "$original_query" != "$query" ]; then
486
- rmdir "$lock" 2>/dev/null || true
487
- return 2
488
- fi
489
- [ -n "$previous_terms" ] || {
490
- rmdir "$lock" 2>/dev/null || true
491
- return 2
492
- }
493
- missing_previous_terms="$(jq -nr \
494
- --argjson previous "$previous_terms" \
495
- --argjson current "$current_terms" \
496
- '($previous - $current) | length' 2>/dev/null)" || {
497
- rmdir "$lock" 2>/dev/null || true
498
- return 2
499
- }
500
- new_terms="$(jq -nr \
501
- --argjson previous "$previous_terms" \
502
- --argjson current "$current_terms" \
503
- '($current - $previous) | length' 2>/dev/null)" || {
504
- rmdir "$lock" 2>/dev/null || true
505
- return 2
506
- }
507
- if [ "$missing_previous_terms" -ne 0 ] || [ "$new_terms" -eq 0 ]; then
508
- # A semantic retry must be cumulative and add at least one new term.
509
- # Do not consume a retry budget for an unchanged or replaced term set.
510
- rmdir "$lock" 2>/dev/null || true
511
- return 3
512
- fi
513
- fi
514
- case "$retry_count" in
515
- ''|*[!0-9]*) retry_count="$MCC_SEMANTIC_RETRY_LIMIT" ;;
516
- esac
517
- if [ "$retry_count" -ge "$MCC_SEMANTIC_RETRY_LIMIT" ]; then
418
+ [ "$lock_acquired" = true ] || return 1
419
+ [ -f "$file" ] && [ ! -L "$file" ] || {
518
420
  rmdir "$lock" 2>/dev/null || true
519
- return 2
520
- fi
521
- next_retry_count=$((retry_count + 1))
522
- now="$(date +%s 2>/dev/null)" || {
523
- rmdir "$lock" 2>/dev/null || true
524
- return 2
421
+ return 1
525
422
  }
526
- tmp="$(mktemp "$dir/.mcc-semantic-retries.XXXXXX" 2>/dev/null)" || {
423
+ tmp="$(mktemp "$dir/.mcc-agent-results.XXXXXX" 2>/dev/null)" || {
527
424
  rmdir "$lock" 2>/dev/null || true
528
- return 2
425
+ return 1
529
426
  }
530
427
  chmod 600 "$tmp" 2>/dev/null || true
531
- if [ -f "$file" ] && [ ! -L "$file" ]; then
532
- if ! jq -ce \
533
- --arg query "$original_query" \
534
- --arg inputId "$input_id" \
535
- --argjson count "$next_retry_count" \
536
- --argjson now "$now" \
537
- --argjson semanticTerms "$current_terms" '
538
- if .schemaVersion != 1 or ((.entries // null) | type != "array") then
539
- error("invalid MCC semantic retry state")
540
- else
541
- .entries = (
542
- [.entries[] | select(.inputId != $inputId)]
543
- + [{
544
- query: $query,
545
- inputId: $inputId,
546
- count: $count,
547
- updatedAtEpoch: $now,
548
- semanticTerms: $semanticTerms
549
- }]
550
- )
551
- | .entries |= sort_by(.updatedAtEpoch)
552
- end
553
- ' "$file" >"$tmp" 2>/dev/null; then
554
- rm -f "$tmp" 2>/dev/null || true
555
- rmdir "$lock" 2>/dev/null || true
556
- return 2
557
- fi
558
- elif [ -e "$file" ]; then
559
- rm -f "$tmp" 2>/dev/null || true
560
- rmdir "$lock" 2>/dev/null || true
561
- return 2
562
- else
563
- if ! jq -cn \
564
- --arg query "$original_query" \
565
- --arg inputId "$input_id" \
566
- --argjson count "$next_retry_count" \
567
- --argjson now "$now" \
568
- --argjson semanticTerms "$current_terms" \
569
- '{
570
- schemaVersion: 1,
571
- entries: [{
428
+ if ! jq -ce \
429
+ --arg query "$query" \
430
+ --arg inputId "$input_id" \
431
+ --arg catalogDigest "$catalog_digest" \
432
+ --argjson candidateCodes "$candidate_codes" '
433
+ if .schemaVersion != 3 or ((.entries // null) | type != "array") then
434
+ error("invalid MCC agent review state")
435
+ elif (any(.entries[];
436
+ .query == $query and .inputId == $inputId and .phase == "review" and .catalogDigest == $catalogDigest
437
+ ) | not) then
438
+ error("MCC agent review was not issued")
439
+ elif any(.entries[];
440
+ .query == $query and .inputId == $inputId and .phase == "result"
441
+ and .catalogDigest == $catalogDigest and .candidateCodes == $candidateCodes
442
+ ) then
443
+ .
444
+ elif any(.entries[]; .query == $query and .inputId == $inputId and .phase == "result") then
445
+ error("MCC agent result already bound")
446
+ else
447
+ .entries += [{
572
448
  query: $query,
573
449
  inputId: $inputId,
574
- count: $count,
575
- updatedAtEpoch: $now,
576
- semanticTerms: $semanticTerms
450
+ phase: "result",
451
+ catalogDigest: $catalogDigest,
452
+ candidateCodes: $candidateCodes
577
453
  }]
578
- }' >"$tmp" 2>/dev/null; then
579
- rm -f "$tmp" 2>/dev/null || true
580
- rmdir "$lock" 2>/dev/null || true
581
- return 2
582
- fi
454
+ end
455
+ ' "$file" >"$tmp" 2>/dev/null; then
456
+ rm -f "$tmp" 2>/dev/null || true
457
+ rmdir "$lock" 2>/dev/null || true
458
+ return 1
583
459
  fi
584
460
  if ! mv "$tmp" "$file" 2>/dev/null; then
585
461
  rm -f "$tmp" 2>/dev/null || true
586
462
  rmdir "$lock" 2>/dev/null || true
587
- return 2
463
+ return 1
588
464
  fi
589
465
  rmdir "$lock" 2>/dev/null || true
590
- printf '%s' "$next_retry_count"
466
+ return 0
591
467
  }
592
468
 
593
469
  get_mcc_tree() {
@@ -812,8 +688,9 @@ normalize_mcc_catalog() {
812
688
  }
813
689
 
814
690
  run_mcc_match_codes() {
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
691
+ local query="$1" product_type="$2" root_intent="$3" catalog="$4" agent_candidate_refs_json="${5:-}" input_id="${6:-}" agent_candidate_refs_provided="${7:-false}" runner_out runner_err runner_rc line resolution=""
692
+ local runner_error error_kind repair_payload semantic_status candidate_codes catalog_digest
693
+ catalog_digest="$(mcc_catalog_digest "$catalog")" || mcc_query_failed
817
694
  runner_out=$(mktemp "${TMPDIR:-/tmp}/mcc_match_out.XXXXXX") || { echo "❌ 创建临时文件失败"; exit 1; }
818
695
  runner_err=$(mktemp "${TMPDIR:-/tmp}/mcc_match_err.XXXXXX") || { rm -f "$runner_out"; echo "❌ 创建临时文件失败"; exit 1; }
819
696
  local node_args=(
@@ -821,10 +698,9 @@ run_mcc_match_codes() {
821
698
  --query "$query" \
822
699
  --product-type "$product_type" \
823
700
  --root-intent "$root_intent" \
824
- --mcc-catalog-json "$catalog" \
825
- --semantic-terms-json "$semantic_terms_json"
701
+ --mcc-catalog-json "$catalog"
826
702
  )
827
- if [ -n "$agent_candidate_refs_json" ]; then
703
+ if [ "$agent_candidate_refs_provided" = true ]; then
828
704
  node_args+=(--agent-candidate-refs-json "$agent_candidate_refs_json")
829
705
  fi
830
706
  "${NODE_BINARY:-node}" "${node_args[@]}" >"$runner_out" 2>"$runner_err"
@@ -832,11 +708,31 @@ run_mcc_match_codes() {
832
708
  if [ "$runner_rc" -ne 0 ] || [ -s "$runner_out" ]; then
833
709
  runner_error="$(sed -n 's/^ONBOARDING_MESSAGE_ERROR://p' "$runner_err" | sed -n '1p')"
834
710
  error_kind="$(mcc_match_error_kind "$runner_error")"
835
- if [ "$error_kind" = "INVALID_AGENT_CANDIDATE_REFS" ] && [ -n "$agent_candidate_refs_json" ]; then
711
+ if [ "$error_kind" = "INVALID_AGENT_CANDIDATE_REFS" ] && [ "$agent_candidate_refs_provided" = true ]; then
836
712
  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 $?
713
+ if claim_mcc_agent_phase "$product_type" "$(product_sales_code "$product_type")" "$query" "$input_id" repair "$catalog_digest"; then
714
+ repair_payload=$(jq -cn \
715
+ --arg reason "$error_kind" \
716
+ --argjson catalog "$catalog" '
717
+ {
718
+ required: true,
719
+ attempt: 2,
720
+ maxAttempts: 2,
721
+ reason: $reason,
722
+ instructions: {
723
+ returnOnlyCatalogRefs: true,
724
+ deduplicate: true,
725
+ rankByRelevance: true,
726
+ maxCandidates: 8
727
+ },
728
+ catalog: ($catalog | map({ref: .mccRef, mccName, mccDescription}))
729
+ }
730
+ ') || mcc_query_failed
731
+ jq -cn --argjson agentRepair "$repair_payload" '{matchType:"REPAIR", agentRepair:$agentRepair}'
732
+ return 0
733
+ fi
734
+ emit_control "MCC_MATCH_ERROR=AGENT_REPAIR_EXHAUSTED"
735
+ mcc_query_failed
840
736
  fi
841
737
  rm -f "$runner_out" "$runner_err"
842
738
  emit_control "MCC_MATCH_ERROR=$error_kind"
@@ -850,21 +746,30 @@ run_mcc_match_codes() {
850
746
  done <"$runner_err"
851
747
  rm -f "$runner_out" "$runner_err"
852
748
  [ -n "$resolution" ] || mcc_query_failed
749
+ if [ "$agent_candidate_refs_provided" = true ]; then
750
+ semantic_status=$(printf '%s' "$resolution" | jq -r '.semanticStatus // ""' 2>/dev/null) || mcc_query_failed
751
+ if [ "$semantic_status" = "AGENT_CATALOG_REVIEW_USED" ]; then
752
+ candidate_codes=$(printf '%s' "$resolution" | jq -c '.agentCandidateCodes // []' 2>/dev/null) || mcc_query_failed
753
+ if ! bind_mcc_agent_result "$product_type" "$(product_sales_code "$product_type")" "$query" "$input_id" "$candidate_codes" "$catalog_digest"; then
754
+ emit_control "MCC_MATCH_ERROR=AGENT_RESULT_STATE_INVALID"
755
+ mcc_query_failed
756
+ fi
757
+ fi
758
+ fi
853
759
  printf '%s' "$resolution"
854
760
  }
855
761
 
856
762
  validate_mcc_selection_cache() {
857
- local query="$1" product_type="$2" selection_receipt="$3" catalog="$4" semantic_terms_json="$5"
763
+ local query="$1" product_type="$2" selection_receipt="$3" catalog="$4"
858
764
  "${NODE_BINARY:-node}" "$ONBOARDING_RUNNER" mcc-validate-selection-cache \
859
765
  --query "$query" \
860
766
  --product-type "$product_type" \
861
767
  --selection-receipt "$selection_receipt" \
862
- --mcc-catalog-json "$catalog" \
863
- --semantic-terms-json "$semantic_terms_json" >/dev/null 2>&1
768
+ --mcc-catalog-json "$catalog" >/dev/null 2>&1
864
769
  }
865
770
 
866
771
  run_resolve_plan() {
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=""
772
+ local query="" product_type="" root_intent="" agent_candidate_refs_json="" agent_candidate_refs_provided=false select_value="" selection_receipt="" input_id="" sales_code tree rows tree_catalog tree_catalog_digest resolution match_type full_codes candidate_rows qualification_rows details catalog selection_cache="" used_selection_cache=false stale_selection=false agent_review_required="false" agent_review_payload="" agent_repair_required="false" agent_repair_payload=""
868
773
  shift
869
774
  while [[ $# -gt 0 ]]; do
870
775
  case "$1" in
@@ -877,12 +782,9 @@ run_resolve_plan() {
877
782
  --root-intent)
878
783
  [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; exit 1; }
879
784
  root_intent="$2"; shift 2 ;;
880
- --semantic-terms-json)
881
- [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; exit 1; }
882
- semantic_terms_json="$2"; shift 2 ;;
883
785
  --agent-candidate-refs-json)
884
786
  [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; exit 1; }
885
- agent_candidate_refs_json="$2"; shift 2 ;;
787
+ agent_candidate_refs_json="$2"; agent_candidate_refs_provided=true; shift 2 ;;
886
788
  --select)
887
789
  [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; exit 1; }
888
790
  select_value="$2"; shift 2 ;;
@@ -907,12 +809,6 @@ run_resolve_plan() {
907
809
  exit 1
908
810
  ;;
909
811
  esac
910
- semantic_terms_json=$(printf '%s' "$semantic_terms_json" | jq -ce '
911
- select(type == "array" and length <= 12 and all(.[]; type == "string"))
912
- ' 2>/dev/null) || {
913
- echo "❌ --semantic-terms-json 必须是最多 12 项的 JSON 字符串数组"
914
- exit 1
915
- }
916
812
  sales_code=$(product_sales_code "$product_type") || {
917
813
  echo "❌ product-type 非法,仅支持 aipay|webpay|apppay"
918
814
  exit 1
@@ -925,7 +821,7 @@ run_resolve_plan() {
925
821
  candidate_rows=$(printf '%s' "$selection_cache" | jq -c '.candidateRows' 2>/dev/null) || mcc_query_failed
926
822
  details=$(printf '%s' "$selection_cache" | jq -c '.details' 2>/dev/null) || mcc_query_failed
927
823
  catalog=$(normalize_mcc_catalog "$candidate_rows" "$details" 2>/dev/null || true)
928
- if [ -n "$catalog" ] && validate_mcc_selection_cache "$query" "$product_type" "$selection_receipt" "$catalog" "$semantic_terms_json"; then
824
+ if [ -n "$catalog" ] && validate_mcc_selection_cache "$query" "$product_type" "$selection_receipt" "$catalog"; then
929
825
  used_selection_cache=true
930
826
  else
931
827
  catalog=""
@@ -945,54 +841,29 @@ run_resolve_plan() {
945
841
  tree_catalog=$(normalize_mcc_tree_catalog "$rows") || {
946
842
  mcc_query_failed
947
843
  }
948
- capture_or_exit resolution run_mcc_match_codes "$query" "$product_type" "$root_intent" "$tree_catalog" "$semantic_terms_json" "$agent_candidate_refs_json"
844
+ tree_catalog_digest="$(mcc_catalog_digest "$tree_catalog")" || mcc_query_failed
845
+ capture_or_exit resolution run_mcc_match_codes "$query" "$product_type" "$root_intent" "$tree_catalog" "$agent_candidate_refs_json" "$input_id" "$agent_candidate_refs_provided"
949
846
  match_type=$(printf '%s' "$resolution" | jq -r '.matchType // ""' 2>/dev/null) || mcc_query_failed
950
- semantic_status=$(printf '%s' "$resolution" | jq -r '.semanticStatus // ""' 2>/dev/null) || mcc_query_failed
951
- resolved_semantic_terms=$(printf '%s' "$resolution" | jq -c '.semanticTerms // []' 2>/dev/null) || mcc_query_failed
952
847
  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
956
- if [ "$agent_review_required" = "true" ] && [ -z "$agent_candidate_refs_json" ] && [ -n "$input_id" ]; then
957
- agent_review_payload=$(printf '%s' "$resolution" | jq -c '.agentReview' 2>/dev/null) || mcc_query_failed
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
848
+ agent_repair_required=$(printf '%s' "$resolution" | jq -r '.agentRepair.required // false' 2>/dev/null) || mcc_query_failed
849
+ if [ "$agent_repair_required" = "true" ]; then
850
+ agent_repair_payload=$(printf '%s' "$resolution" | jq -c '.agentRepair' 2>/dev/null) || mcc_query_failed
851
+ emit_control "MCC_QUERY_RESULT=SUCCESS"
852
+ emit_control "MCC_AGENT_REPAIR_REQUIRED=$agent_repair_payload"
853
+ return 0
965
854
  fi
966
- if [ "$match_type" = "NONE" ] \
967
- && [ "$semantic_status" != "DYNAMIC_CATALOG_CODE_NOT_FOUND" ] \
968
- && [ "$semantic_status" != "DYNAMIC_CATALOG_CODE_AMBIGUOUS" ]; then
969
- if [ -n "$input_id" ]; then
970
- next_retry_count="$(claim_mcc_semantic_retry \
971
- "$product_type" "$sales_code" "$query" "$input_id" "$resolved_semantic_terms" 2>/dev/null)"
972
- retry_rc=$?
973
- else
974
- # A retry without a message identity cannot be bound to one user input.
975
- # Keep the bounded retry contract closed instead of sharing a placeholder.
976
- retry_rc=2
977
- fi
978
- if [ "$retry_rc" -eq 0 ]; then
979
- if [ "$semantic_terms_json" = "[]" ]; then
980
- retry_reason="MISSING_SEMANTIC_TERMS"
981
- else
982
- retry_reason="NO_MATCH"
855
+ if [ "$agent_review_required" = "true" ]; then
856
+ if [ "$agent_candidate_refs_provided" != true ] && [ -n "$input_id" ]; then
857
+ agent_review_payload=$(printf '%s' "$resolution" | jq -c '.agentReview' 2>/dev/null) || mcc_query_failed
858
+ if claim_mcc_agent_phase "$product_type" "$sales_code" "$query" "$input_id" review "$tree_catalog_digest"; then
859
+ emit_control "MCC_QUERY_RESULT=SUCCESS"
860
+ emit_control "MCC_RESOLUTION=$(printf '%s' "$resolution" | jq -c 'del(.agentReview)' 2>/dev/null)"
861
+ emit_control "MCC_AGENT_REVIEW_REQUIRED=$agent_review_payload"
862
+ return 0
983
863
  fi
984
- retry_payload=$(jq -cn \
985
- --argjson attempt "$next_retry_count" \
986
- --argjson maxAttempts "$MCC_SEMANTIC_RETRY_LIMIT" \
987
- --argjson remainingAttempts "$((MCC_SEMANTIC_RETRY_LIMIT - next_retry_count))" \
988
- --arg reason "$retry_reason" \
989
- '{attempt: $attempt, maxAttempts: $maxAttempts, remainingAttempts: $remainingAttempts, reason: $reason}') \
990
- || mcc_query_failed
991
- emit_control "MCC_QUERY_RESULT=SUCCESS"
992
- emit_control "MCC_RESOLUTION=$resolution"
993
- emit_control "MCC_SEMANTIC_RETRY_REQUIRED=$retry_payload"
994
- return 0
995
864
  fi
865
+ emit_control "MCC_MATCH_ERROR=AGENT_REVIEW_STATE_INVALID"
866
+ mcc_query_failed
996
867
  fi
997
868
  if [ "$match_type" = "NONE" ]; then
998
869
  catalog="$tree_catalog"
@@ -1019,27 +890,17 @@ run_resolve_plan() {
1019
890
  fi
1020
891
  fi
1021
892
  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
1031
893
  node_args=(
1032
894
  "$ONBOARDING_RUNNER" mcc-resolve-plan
1033
895
  --query "$query"
1034
896
  --product-type "$product_type"
1035
897
  --root-intent "$root_intent"
1036
898
  --mcc-catalog-json "$catalog"
1037
- --semantic-terms-json "$semantic_terms_json"
1038
899
  )
1039
900
  if [ -n "$input_id" ]; then
1040
901
  node_args+=(--input-id "$input_id")
1041
902
  fi
1042
- if [ -n "$agent_candidate_refs_json" ]; then
903
+ if [ "$agent_candidate_refs_provided" = true ]; then
1043
904
  node_args+=(--agent-candidate-refs-json "$agent_candidate_refs_json")
1044
905
  fi
1045
906
  if [ "$stale_selection" = true ]; then
@@ -1065,7 +926,7 @@ run_resolve_plan() {
1065
926
  local generated_selection_receipt
1066
927
  generated_selection_receipt=$(sed -n 's/^ALIPAY_AIPAY_INTERNAL:MCC_SELECTION_RECEIPT=//p' "$runner_err")
1067
928
  if [ "$(printf '%s\n' "$generated_selection_receipt" | sed '/^$/d' | wc -l | tr -d ' ')" = "1" ]; then
1068
- write_mcc_selection_cache "$product_type" "$sales_code" "$query" "$generated_selection_receipt" "$resolved_semantic_terms" "$candidate_rows" "$details"
929
+ write_mcc_selection_cache "$product_type" "$sales_code" "$query" "$generated_selection_receipt" "$candidate_rows" "$details"
1069
930
  fi
1070
931
  fi
1071
932
  emit_control "MCC_QUERY_RESULT=SUCCESS"
@@ -1079,7 +940,7 @@ case "${1:-}" in
1079
940
  run_resolve_plan "$@"
1080
941
  ;;
1081
942
  *)
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>]"
943
+ echo "用法: bash mcc.sh resolve-plan --query <经营描述|MCC编码> --product-type <aipay|webpay|apppay> --root-intent <onboarding_only|full_process> [--agent-candidate-refs-json <JSON数组>] [--select <MCC编码> --selection-receipt <receipt>] [--input-id <USER_INPUT_ID>]"
1083
944
  exit 1
1084
945
  ;;
1085
946
  esac