@alipay/alipay-aipay 1.8.3 → 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.
- package/package.json +3 -3
- package/skill/VERSION +1 -1
- package/skill/references/normal/scripts/onboarding_message_runner.mjs +80 -250
- package/skill/references/onboarding/flow.md +9 -9
- package/skill/references/onboarding/modules/product-sign.md +1 -1
- package/skill/references/onboarding/modules/scripts/mcc.sh +151 -290
- package/skill/references/runtime-entry.md +1 -1
- package/skill-pre/VERSION +1 -1
- package/skill-pre/references/normal/scripts/onboarding_message_runner.mjs +80 -250
- package/skill-pre/references/onboarding/flow.md +9 -9
- package/skill-pre/references/onboarding/modules/product-sign.md +1 -1
- package/skill-pre/references/onboarding/modules/scripts/mcc.sh +151 -290
- package/skill-pre/references/runtime-entry.md +1 -1
|
@@ -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> [--
|
|
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 "
|
|
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 ==
|
|
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"
|
|
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:
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
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[]; .
|
|
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
|
-
|
|
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
|
-
|
|
413
|
-
local product_type="$1" sales_code="$2" query="$3" input_id="$4"
|
|
414
|
-
local file dir lock
|
|
415
|
-
|
|
416
|
-
|
|
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
|
|
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
|
|
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-
|
|
423
|
+
tmp="$(mktemp "$dir/.mcc-agent-results.XXXXXX" 2>/dev/null)" || {
|
|
527
424
|
rmdir "$lock" 2>/dev/null || true
|
|
528
|
-
return
|
|
425
|
+
return 1
|
|
529
426
|
}
|
|
530
427
|
chmod 600 "$tmp" 2>/dev/null || true
|
|
531
|
-
if
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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
|
-
|
|
575
|
-
|
|
576
|
-
|
|
450
|
+
phase: "result",
|
|
451
|
+
catalogDigest: $catalogDigest,
|
|
452
|
+
candidateCodes: $candidateCodes
|
|
577
453
|
}]
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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
|
|
463
|
+
return 1
|
|
588
464
|
fi
|
|
589
465
|
rmdir "$lock" 2>/dev/null || true
|
|
590
|
-
|
|
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"
|
|
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 [
|
|
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" ] && [
|
|
711
|
+
if [ "$error_kind" = "INVALID_AGENT_CANDIDATE_REFS" ] && [ "$agent_candidate_refs_provided" = true ]; then
|
|
836
712
|
rm -f "$runner_out" "$runner_err"
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
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"
|
|
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=""
|
|
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"
|
|
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
|
-
|
|
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
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
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 [ "$
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
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 [
|
|
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" "$
|
|
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> [--
|
|
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
|