@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.
- package/package.json +3 -3
- package/skill/VERSION +1 -1
- package/skill/references/integration/flow.md +27 -30
- package/skill/references/integration/modules/checklist.md +4 -6
- package/skill/references/integration/modules/sandbox/sandbox-setup-guide.md +2 -2
- package/skill/references/normal/integration-worker.md +1 -1
- package/skill/references/normal/json/customer-messages.json +1 -1
- package/skill/references/normal/scripts/flow_loader.mjs +9 -0
- package/skill/references/normal/scripts/integration_message_runner.mjs +81 -14
- package/skill/references/normal/scripts/onboarding_message_runner.mjs +80 -250
- package/skill/references/normal/scripts/render_customer_message.mjs +6 -6
- 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 +2 -2
- package/skill-pre/VERSION +1 -1
- package/skill-pre/references/integration/flow.md +27 -30
- package/skill-pre/references/integration/modules/checklist.md +4 -6
- package/skill-pre/references/integration/modules/sandbox/sandbox-setup-guide.md +2 -2
- package/skill-pre/references/normal/integration-worker.md +1 -1
- package/skill-pre/references/normal/json/customer-messages.json +1 -1
- package/skill-pre/references/normal/scripts/flow_loader.mjs +9 -0
- package/skill-pre/references/normal/scripts/integration_message_runner.mjs +81 -14
- package/skill-pre/references/normal/scripts/onboarding_message_runner.mjs +80 -250
- package/skill-pre/references/normal/scripts/render_customer_message.mjs +6 -6
- 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 +2 -2
|
@@ -244,13 +244,6 @@ function assertMccQuery(query) {
|
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
const mccSelectionReceiptPrefix = 'AIPAY_MCC_SELECTION_V1.';
|
|
247
|
-
const ignoredMccQueryTokens = new Set([
|
|
248
|
-
'服务', '企业', '个人', '提供', '适用', '用于', '平台', '项目', '申请', '商家', '公司',
|
|
249
|
-
'互联', '联网', '互联网', '在线', '用品', '收费', '缴费', '费用', '适用于', '经营',
|
|
250
|
-
'业务', '相关', '主体', '从事', '网站', '网页', '购买', '买票', '购票', '票务', '售票',
|
|
251
|
-
'预约', '预定', '退票', '支付', '付款', '收款', '下单', '订单', '交易', '销售', '售卖',
|
|
252
|
-
'商品', '产品'
|
|
253
|
-
]);
|
|
254
247
|
|
|
255
248
|
function activeMccRunId(env = process.env) {
|
|
256
249
|
const candidate = env.ALIPAY_AIPAY_RUN_ID
|
|
@@ -378,8 +371,10 @@ function mccCatalogForAgent(records) {
|
|
|
378
371
|
}
|
|
379
372
|
|
|
380
373
|
function parseAgentCandidateRefs(args, records) {
|
|
381
|
-
|
|
382
|
-
|
|
374
|
+
if (!Object.prototype.hasOwnProperty.call(args, 'agent-candidate-refs-json')) {
|
|
375
|
+
return { provided: false, codes: [] };
|
|
376
|
+
}
|
|
377
|
+
const raw = args['agent-candidate-refs-json'];
|
|
383
378
|
let parsed;
|
|
384
379
|
try {
|
|
385
380
|
parsed = JSON.parse(raw);
|
|
@@ -397,7 +392,7 @@ function parseAgentCandidateRefs(args, records) {
|
|
|
397
392
|
}
|
|
398
393
|
codes.push(refs.get(ref));
|
|
399
394
|
}
|
|
400
|
-
return { codes };
|
|
395
|
+
return { provided: true, codes };
|
|
401
396
|
}
|
|
402
397
|
|
|
403
398
|
function mccTableSummary(records) {
|
|
@@ -435,190 +430,12 @@ function directMccCode(query, records) {
|
|
|
435
430
|
return { isCode: true, matches: [...records.keys()].filter((code) => code.endsWith(`_${raw}`)) };
|
|
436
431
|
}
|
|
437
432
|
|
|
438
|
-
function
|
|
439
|
-
const grams = new Set();
|
|
440
|
-
const searchableText = meaningfulMccSearch(value);
|
|
441
|
-
for (const segment of searchableText.split(/\s+/u).filter(Boolean)) {
|
|
442
|
-
const characters = [...segment];
|
|
443
|
-
for (let index = 0; index <= characters.length - size; index += 1) {
|
|
444
|
-
const gram = characters.slice(index, index + size).join('');
|
|
445
|
-
if (/^[\p{L}\p{N}]+$/u.test(gram)) grams.add(gram);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
return grams;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
function meaningfulMccSearch(value) {
|
|
452
|
-
let searchableText = normalizeSearch(value);
|
|
453
|
-
const ignoredTerms = [...ignoredMccQueryTokens]
|
|
454
|
-
.sort((left, right) => [...right].length - [...left].length);
|
|
455
|
-
for (const term of ignoredTerms) searchableText = searchableText.replaceAll(term, ' ');
|
|
456
|
-
return searchableText
|
|
457
|
-
.trim()
|
|
458
|
-
.replace(/\s+/gu, ' ')
|
|
459
|
-
.split(' ')
|
|
460
|
-
.filter((segment) => [...segment].length >= 2)
|
|
461
|
-
.join(' ');
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
function parseMccSemanticTerms(args) {
|
|
465
|
-
const raw = optional(args, 'semantic-terms-json');
|
|
466
|
-
if (raw === undefined) return [];
|
|
467
|
-
let parsed;
|
|
468
|
-
try {
|
|
469
|
-
parsed = JSON.parse(raw);
|
|
470
|
-
} catch {
|
|
471
|
-
throw new Error('semantic-terms-json 必须是合法 JSON 字符串数组');
|
|
472
|
-
}
|
|
473
|
-
if (!Array.isArray(parsed) || parsed.length > 12 || parsed.some((term) => typeof term !== 'string')) {
|
|
474
|
-
throw new Error('semantic-terms-json 必须是最多 12 项的 JSON 字符串数组');
|
|
475
|
-
}
|
|
476
|
-
const normalized = [];
|
|
477
|
-
let totalLength = 0;
|
|
478
|
-
for (const rawTerm of parsed) {
|
|
479
|
-
if (/\u0000|[\u0001-\u001f\u007f]/u.test(rawTerm)) {
|
|
480
|
-
throw new Error('semantic-terms-json 包含控制字符');
|
|
481
|
-
}
|
|
482
|
-
const term = normalizeSearch(rawTerm);
|
|
483
|
-
const termLength = [...term.replace(/\s+/gu, '')].length;
|
|
484
|
-
if (termLength < 2 || termLength > 40) {
|
|
485
|
-
throw new Error('semantic-terms-json 每项长度必须为 2 到 40 个字符');
|
|
486
|
-
}
|
|
487
|
-
if (/(?:A\d{4}\s*)?B\d{4}/iu.test(term)) {
|
|
488
|
-
throw new Error('semantic-terms-json 只能包含业务概念词,禁止提供 MCC 编码');
|
|
489
|
-
}
|
|
490
|
-
totalLength += termLength;
|
|
491
|
-
if (!normalized.includes(term)) normalized.push(term);
|
|
492
|
-
}
|
|
493
|
-
if (totalLength > 240) throw new Error('semantic-terms-json 总长度不得超过 240 个字符');
|
|
494
|
-
return normalized.sort((left, right) => (left < right ? -1 : left > right ? 1 : 0));
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
function mccDocumentFrequency(indexedRecords) {
|
|
498
|
-
const frequencies = new Map();
|
|
499
|
-
for (const indexed of indexedRecords) {
|
|
500
|
-
const documentGrams = new Set([
|
|
501
|
-
...indexed.nameBigrams,
|
|
502
|
-
...indexed.nameTrigrams,
|
|
503
|
-
...indexed.businessBigrams,
|
|
504
|
-
...indexed.businessTrigrams
|
|
505
|
-
]);
|
|
506
|
-
for (const gram of documentGrams) frequencies.set(gram, (frequencies.get(gram) || 0) + 1);
|
|
507
|
-
}
|
|
508
|
-
return frequencies;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
function weightedMccOverlap(queryGrams, documentGrams, frequencies, recordCount, size) {
|
|
512
|
-
let weight = 0;
|
|
513
|
-
for (const gram of queryGrams) {
|
|
514
|
-
if (!documentGrams.has(gram)) continue;
|
|
515
|
-
const documentCount = frequencies.get(gram) || 0;
|
|
516
|
-
weight += (Math.log((recordCount + 1) / (documentCount + 1)) + 1) * (size === 3 ? 1.6 : 1);
|
|
517
|
-
}
|
|
518
|
-
return weight;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
function mccPhraseCoverage(haystackSegments, needleSegments) {
|
|
522
|
-
let coverage = 0;
|
|
523
|
-
for (const needle of needleSegments) {
|
|
524
|
-
if (ignoredMccQueryTokens.has(needle)) continue;
|
|
525
|
-
const needleLength = [...needle].length;
|
|
526
|
-
for (const haystack of haystackSegments) {
|
|
527
|
-
if (!haystack.includes(needle)) continue;
|
|
528
|
-
coverage = Math.max(coverage, needleLength / [...haystack].length);
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
return coverage;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function mccTextEvidence(value, indexed, frequencies, recordCount) {
|
|
535
|
-
const normalizedValue = normalizeSearch(value);
|
|
536
|
-
const valueSegments = meaningfulMccSearch(normalizedValue).split(' ').filter(Boolean);
|
|
537
|
-
const valueBigrams = mccNgrams(normalizedValue, 2);
|
|
538
|
-
const valueTrigrams = mccNgrams(normalizedValue, 3);
|
|
539
|
-
const exactName = indexed.name === normalizedValue
|
|
540
|
-
|| (normalizedValue.split(' ').length === 1 && indexed.nameSegments.includes(normalizedValue));
|
|
541
|
-
const nameContainsValueCoverage = mccPhraseCoverage(indexed.nameEvidenceSegments, valueSegments);
|
|
542
|
-
const valueContainsNameCoverage = mccPhraseCoverage(valueSegments, indexed.nameEvidenceSegments);
|
|
543
|
-
const businessContainsValueCoverage = mccPhraseCoverage(indexed.businessEvidenceSegments, valueSegments);
|
|
544
|
-
const nameBigram = weightedMccOverlap(valueBigrams, indexed.nameBigrams, frequencies, recordCount, 2);
|
|
545
|
-
const nameTrigram = weightedMccOverlap(valueTrigrams, indexed.nameTrigrams, frequencies, recordCount, 3);
|
|
546
|
-
const businessBigram = weightedMccOverlap(valueBigrams, indexed.businessBigrams, frequencies, recordCount, 2);
|
|
547
|
-
const businessTrigram = weightedMccOverlap(valueTrigrams, indexed.businessTrigrams, frequencies, recordCount, 3);
|
|
548
|
-
const matchedBigrams = new Set([
|
|
549
|
-
...[...valueBigrams].filter((gram) => indexed.nameBigrams.has(gram)),
|
|
550
|
-
...[...valueBigrams].filter((gram) => indexed.businessBigrams.has(gram))
|
|
551
|
-
]);
|
|
552
|
-
const matchedTrigrams = new Set([
|
|
553
|
-
...[...valueTrigrams].filter((gram) => indexed.nameTrigrams.has(gram)),
|
|
554
|
-
...[...valueTrigrams].filter((gram) => indexed.businessTrigrams.has(gram))
|
|
555
|
-
]);
|
|
556
|
-
const rareBigramDocumentLimit = Math.max(1, Math.ceil(recordCount * 0.02));
|
|
557
|
-
const hasRareBigram = [...matchedBigrams].some((gram) => (
|
|
558
|
-
(frequencies.get(gram) || recordCount) <= rareBigramDocumentLimit
|
|
559
|
-
));
|
|
560
|
-
const hasLexicalEvidence = matchedTrigrams.size > 0
|
|
561
|
-
|| matchedBigrams.size >= 2
|
|
562
|
-
|| (valueBigrams.size <= 2 && hasRareBigram);
|
|
563
|
-
const phraseEvidence = nameContainsValueCoverage > 0
|
|
564
|
-
|| valueContainsNameCoverage > 0
|
|
565
|
-
|| businessContainsValueCoverage > 0;
|
|
566
|
-
return {
|
|
567
|
-
eligible: exactName || phraseEvidence || hasLexicalEvidence,
|
|
568
|
-
exactName,
|
|
569
|
-
score: (exactName ? 100000 : 0)
|
|
570
|
-
+ (nameContainsValueCoverage * 6000)
|
|
571
|
-
+ (valueContainsNameCoverage * 3000)
|
|
572
|
-
+ (businessContainsValueCoverage * 2500)
|
|
573
|
-
+ ((nameBigram + nameTrigram) * 120)
|
|
574
|
-
+ ((businessBigram + businessTrigram) * 40)
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
function deterministicMccMatches(query, records, { limit = 8, semanticTerms = [] } = {}) {
|
|
433
|
+
function directMccName(query, records) {
|
|
579
434
|
const normalizedQuery = assertMccQuery(query);
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
record,
|
|
585
|
-
name,
|
|
586
|
-
nameSegments: name.split(' ').filter(Boolean),
|
|
587
|
-
nameEvidenceSegments: meaningfulMccSearch(name).split(' ').filter(Boolean),
|
|
588
|
-
businessEvidenceSegments: meaningfulMccSearch(businesses).split(' ').filter(Boolean),
|
|
589
|
-
nameBigrams: mccNgrams(name, 2),
|
|
590
|
-
nameTrigrams: mccNgrams(name, 3),
|
|
591
|
-
businessBigrams: mccNgrams(businesses, 2),
|
|
592
|
-
businessTrigrams: mccNgrams(businesses, 3)
|
|
593
|
-
};
|
|
594
|
-
});
|
|
595
|
-
const frequencies = mccDocumentFrequency(indexedRecords);
|
|
596
|
-
const scored = [];
|
|
597
|
-
|
|
598
|
-
for (const indexed of indexedRecords) {
|
|
599
|
-
const originalEvidence = mccTextEvidence(normalizedQuery, indexed, frequencies, indexedRecords.length);
|
|
600
|
-
const semanticEvidence = semanticTerms
|
|
601
|
-
.map((term) => mccTextEvidence(term, indexed, frequencies, indexedRecords.length))
|
|
602
|
-
.filter((evidence) => evidence.eligible)
|
|
603
|
-
.sort((left, right) => right.score - left.score)[0];
|
|
604
|
-
if (!originalEvidence.eligible && !semanticEvidence) continue;
|
|
605
|
-
scored.push({
|
|
606
|
-
record: indexed.record,
|
|
607
|
-
score: originalEvidence.score + ((semanticEvidence?.score || 0) * 0.7),
|
|
608
|
-
exactName: originalEvidence.exactName,
|
|
609
|
-
originalEligible: originalEvidence.eligible,
|
|
610
|
-
semanticEligible: Boolean(semanticEvidence),
|
|
611
|
-
semanticExactName: Boolean(semanticEvidence?.exactName)
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
const exactMatches = scored.filter(({ exactName }) => exactName);
|
|
616
|
-
const ranked = exactMatches.length > 0
|
|
617
|
-
? exactMatches
|
|
618
|
-
: scored;
|
|
619
|
-
return ranked
|
|
620
|
-
.sort((left, right) => right.score - left.score || left.record.code.localeCompare(right.record.code))
|
|
621
|
-
.slice(0, limit);
|
|
435
|
+
return sortedMccRecords(records)
|
|
436
|
+
.filter((record) => normalizeSearch(record.name) === normalizedQuery)
|
|
437
|
+
.map((record) => record.code)
|
|
438
|
+
.slice(0, 8);
|
|
622
439
|
}
|
|
623
440
|
|
|
624
441
|
function createMccSelectionReceipt(productType, query, result, mccRunContext = captureMccRunContext()) {
|
|
@@ -628,7 +445,6 @@ function createMccSelectionReceipt(productType, query, result, mccRunContext = c
|
|
|
628
445
|
mccRunContext.runId,
|
|
629
446
|
productType,
|
|
630
447
|
assertMccQuery(query),
|
|
631
|
-
JSON.stringify(result.semanticTerms || []),
|
|
632
448
|
table.sha256,
|
|
633
449
|
table.recordCount,
|
|
634
450
|
codes
|
|
@@ -636,8 +452,8 @@ function createMccSelectionReceipt(productType, query, result, mccRunContext = c
|
|
|
636
452
|
return `${mccSelectionReceiptPrefix}${codes}.${createHash('sha256').update(binding).digest('base64url')}`;
|
|
637
453
|
}
|
|
638
454
|
|
|
639
|
-
function resolveMccSelection(productType, query, selectedInput, receipt, records,
|
|
640
|
-
const candidateCodes = mccSelectionReceiptCodes(productType, query, receipt, records,
|
|
455
|
+
function resolveMccSelection(productType, query, selectedInput, receipt, records, mccRunContext) {
|
|
456
|
+
const candidateCodes = mccSelectionReceiptCodes(productType, query, receipt, records, mccRunContext);
|
|
641
457
|
if (!candidateCodes) {
|
|
642
458
|
const semanticStatus = typeof receipt === 'string' && receipt.startsWith(mccSelectionReceiptPrefix)
|
|
643
459
|
? 'SELECTION_RECEIPT_STALE'
|
|
@@ -646,10 +462,8 @@ function resolveMccSelection(productType, query, selectedInput, receipt, records
|
|
|
646
462
|
query,
|
|
647
463
|
productType,
|
|
648
464
|
records,
|
|
649
|
-
semanticTerms,
|
|
650
465
|
semanticStatus,
|
|
651
|
-
mccRunContext
|
|
652
|
-
agentCandidateCodes
|
|
466
|
+
mccRunContext
|
|
653
467
|
);
|
|
654
468
|
return {
|
|
655
469
|
...refreshed,
|
|
@@ -666,8 +480,7 @@ function resolveMccSelection(productType, query, selectedInput, receipt, records
|
|
|
666
480
|
deterministicCandidateCodes: [],
|
|
667
481
|
deterministicStrongCandidateCodes: [],
|
|
668
482
|
deterministicWeakCandidateCodes: [],
|
|
669
|
-
|
|
670
|
-
semanticTerms,
|
|
483
|
+
agentCandidateCodes: [],
|
|
671
484
|
mergedCandidateCodes: candidateCodes,
|
|
672
485
|
mccTableSummary: currentTable,
|
|
673
486
|
candidates: candidateCodes.map((code) => mccCandidateFromRecord(records.get(code), productType, mccRunContext))
|
|
@@ -680,15 +493,14 @@ function resolveMccSelection(productType, query, selectedInput, receipt, records
|
|
|
680
493
|
deterministicCandidateCodes: [],
|
|
681
494
|
deterministicStrongCandidateCodes: [],
|
|
682
495
|
deterministicWeakCandidateCodes: [],
|
|
683
|
-
|
|
684
|
-
semanticTerms,
|
|
496
|
+
agentCandidateCodes: [],
|
|
685
497
|
mergedCandidateCodes: [selectedCode],
|
|
686
498
|
mccTableSummary: currentTable,
|
|
687
499
|
candidates: [candidate]
|
|
688
500
|
};
|
|
689
501
|
}
|
|
690
502
|
|
|
691
|
-
function mccSelectionReceiptCodes(productType, query, receipt, records,
|
|
503
|
+
function mccSelectionReceiptCodes(productType, query, receipt, records, mccRunContext = captureMccRunContext()) {
|
|
692
504
|
if (typeof receipt !== 'string' || !receipt.startsWith(mccSelectionReceiptPrefix)) return null;
|
|
693
505
|
const body = receipt.slice(mccSelectionReceiptPrefix.length);
|
|
694
506
|
const separator = body.lastIndexOf('.');
|
|
@@ -699,8 +511,7 @@ function mccSelectionReceiptCodes(productType, query, receipt, records, semantic
|
|
|
699
511
|
|| candidateCodes.some((code) => !records.has(code))
|
|
700
512
|
|| receipt !== createMccSelectionReceipt(productType, query, {
|
|
701
513
|
mergedCandidateCodes: candidateCodes,
|
|
702
|
-
mccTableSummary: currentTable
|
|
703
|
-
semanticTerms
|
|
514
|
+
mccTableSummary: currentTable
|
|
704
515
|
}, mccRunContext)) {
|
|
705
516
|
return null;
|
|
706
517
|
}
|
|
@@ -711,10 +522,9 @@ function resolveMccResultFromRecords(
|
|
|
711
522
|
query,
|
|
712
523
|
productType,
|
|
713
524
|
records,
|
|
714
|
-
semanticTerms = [],
|
|
715
525
|
semanticStatus,
|
|
716
526
|
mccRunContext = captureMccRunContext(),
|
|
717
|
-
|
|
527
|
+
agentReview = { provided: false, codes: [] }
|
|
718
528
|
) {
|
|
719
529
|
const directCode = directMccCode(query, records);
|
|
720
530
|
if (directCode.isCode) {
|
|
@@ -727,9 +537,7 @@ function resolveMccResultFromRecords(
|
|
|
727
537
|
deterministicCandidateCodes: [code],
|
|
728
538
|
deterministicStrongCandidateCodes: [code],
|
|
729
539
|
deterministicWeakCandidateCodes: [],
|
|
730
|
-
semanticCandidateCodes: [],
|
|
731
540
|
agentCandidateCodes: [],
|
|
732
|
-
semanticTerms,
|
|
733
541
|
mergedCandidateCodes: [code],
|
|
734
542
|
mccTableSummary: mccTableSummary(records),
|
|
735
543
|
candidates: [candidate]
|
|
@@ -742,52 +550,69 @@ function resolveMccResultFromRecords(
|
|
|
742
550
|
deterministicCandidateCodes: finalCodes,
|
|
743
551
|
deterministicStrongCandidateCodes: finalCodes,
|
|
744
552
|
deterministicWeakCandidateCodes: [],
|
|
745
|
-
semanticCandidateCodes: [],
|
|
746
553
|
agentCandidateCodes: [],
|
|
747
|
-
semanticTerms,
|
|
748
554
|
mergedCandidateCodes: finalCodes,
|
|
749
555
|
mccTableSummary: mccTableSummary(records),
|
|
750
556
|
candidates: finalCodes.map((code) => mccCandidateFromRecord(records.get(code), productType, mccRunContext))
|
|
751
557
|
};
|
|
752
558
|
}
|
|
753
559
|
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
560
|
+
const exactNameCodes = directMccName(query, records);
|
|
561
|
+
if (exactNameCodes.length > 0) {
|
|
562
|
+
const candidates = exactNameCodes.map((code) => mccCandidateFromRecord(records.get(code), productType, mccRunContext));
|
|
563
|
+
return {
|
|
564
|
+
matchType: candidates.length === 1 ? 'UNIQUE' : 'MULTIPLE',
|
|
565
|
+
semanticStatus: 'NOT_REQUIRED_EXACT_NAME',
|
|
566
|
+
deterministicCandidateCodes: exactNameCodes,
|
|
567
|
+
deterministicStrongCandidateCodes: exactNameCodes,
|
|
568
|
+
deterministicWeakCandidateCodes: [],
|
|
569
|
+
agentCandidateCodes: [],
|
|
570
|
+
mergedCandidateCodes: exactNameCodes,
|
|
571
|
+
mccTableSummary: mccTableSummary(records),
|
|
572
|
+
candidates
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (!agentReview.provided) {
|
|
577
|
+
return {
|
|
578
|
+
matchType: 'NONE',
|
|
579
|
+
semanticStatus: semanticStatus || 'AGENT_CATALOG_REVIEW_REQUIRED',
|
|
580
|
+
deterministicCandidateCodes: [],
|
|
581
|
+
deterministicStrongCandidateCodes: [],
|
|
582
|
+
deterministicWeakCandidateCodes: [],
|
|
583
|
+
agentCandidateCodes: [],
|
|
584
|
+
mergedCandidateCodes: [],
|
|
585
|
+
mccTableSummary: mccTableSummary(records),
|
|
586
|
+
candidates: [],
|
|
587
|
+
agentReview: {
|
|
588
|
+
required: true,
|
|
589
|
+
attempt: 1,
|
|
590
|
+
maxAttempts: 2,
|
|
591
|
+
reason: 'NATURAL_LANGUAGE_MATCH',
|
|
592
|
+
instructions: {
|
|
593
|
+
keepExplicitConstraints: true,
|
|
594
|
+
keepUnspecifiedDimensionsOpen: true,
|
|
595
|
+
excludeContradictions: true,
|
|
596
|
+
rankByRelevance: true,
|
|
597
|
+
maxCandidates: 8
|
|
598
|
+
},
|
|
599
|
+
catalog: mccCatalogForAgent(records)
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const finalCodes = agentReview.codes;
|
|
762
605
|
const candidates = finalCodes.map((code) => mccCandidateFromRecord(records.get(code), productType, mccRunContext));
|
|
763
|
-
const needsAgentReview = agentCandidateCodes.length === 0
|
|
764
|
-
&& (
|
|
765
|
-
matches.length === 0
|
|
766
|
-
|| deterministicCodes.length !== 1
|
|
767
|
-
|| (semanticTerms.length > 0 && deterministicCodes.length === 0 && !hasExactSemanticName)
|
|
768
|
-
);
|
|
769
606
|
return {
|
|
770
607
|
matchType: candidates.length === 1 ? 'UNIQUE' : candidates.length === 0 ? 'NONE' : 'MULTIPLE',
|
|
771
|
-
semanticStatus: semanticStatus
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
deterministicStrongCandidateCodes: deterministicCodes,
|
|
608
|
+
semanticStatus: semanticStatus || 'AGENT_CATALOG_REVIEW_USED',
|
|
609
|
+
deterministicCandidateCodes: [],
|
|
610
|
+
deterministicStrongCandidateCodes: [],
|
|
775
611
|
deterministicWeakCandidateCodes: [],
|
|
776
|
-
|
|
777
|
-
agentCandidateCodes,
|
|
778
|
-
semanticTerms,
|
|
612
|
+
agentCandidateCodes: finalCodes,
|
|
779
613
|
mergedCandidateCodes: finalCodes,
|
|
780
614
|
mccTableSummary: mccTableSummary(records),
|
|
781
|
-
candidates
|
|
782
|
-
...(needsAgentReview ? {
|
|
783
|
-
agentReview: {
|
|
784
|
-
required: true,
|
|
785
|
-
attempt: 1,
|
|
786
|
-
maxAttempts: 1,
|
|
787
|
-
reason: 'LOW_CONFIDENCE_LOCAL_MATCH',
|
|
788
|
-
catalog: mccCatalogForAgent(records)
|
|
789
|
-
}
|
|
790
|
-
} : {})
|
|
615
|
+
candidates
|
|
791
616
|
};
|
|
792
617
|
}
|
|
793
618
|
|
|
@@ -837,7 +662,9 @@ async function runMccResolvePlan(args) {
|
|
|
837
662
|
const selected = optional(args, 'select');
|
|
838
663
|
const selectionReceipt = optional(args, 'selection-receipt');
|
|
839
664
|
const selectionState = optional(args, 'selection-state');
|
|
840
|
-
|
|
665
|
+
if (args['semantic-terms-json'] !== undefined) {
|
|
666
|
+
throw new Error('mcc-resolve-plan 不再接受 --semantic-terms-json;自然语言只使用一次受限目录判断');
|
|
667
|
+
}
|
|
841
668
|
if ((selected === undefined) !== (selectionReceipt === undefined)) {
|
|
842
669
|
throw new Error('--select 与 --selection-receipt 必须同时提供');
|
|
843
670
|
}
|
|
@@ -853,8 +680,8 @@ async function runMccResolvePlan(args) {
|
|
|
853
680
|
const records = recordsForArgs(args);
|
|
854
681
|
const agentReview = parseAgentCandidateRefs(args, records);
|
|
855
682
|
let result = selected === undefined
|
|
856
|
-
? resolveMccResultFromRecords(query, productType, records,
|
|
857
|
-
: resolveMccSelection(productType, query, selected, selectionReceipt, records,
|
|
683
|
+
? resolveMccResultFromRecords(query, productType, records, undefined, mccRunContext, agentReview)
|
|
684
|
+
: resolveMccSelection(productType, query, selected, selectionReceipt, records, mccRunContext);
|
|
858
685
|
if (selectionState === 'STALE') {
|
|
859
686
|
result = {
|
|
860
687
|
...result,
|
|
@@ -891,14 +718,16 @@ async function runMccMatchCodes(args) {
|
|
|
891
718
|
}
|
|
892
719
|
await startTelemetryRun(rootIntent, productType);
|
|
893
720
|
const query = required(args, 'query');
|
|
894
|
-
|
|
721
|
+
if (args['semantic-terms-json'] !== undefined) {
|
|
722
|
+
throw new Error('mcc-match-codes 不再接受 --semantic-terms-json;自然语言只使用一次受限目录判断');
|
|
723
|
+
}
|
|
895
724
|
if (args['agent-mcc-status'] !== undefined || args['agent-mcc-codes'] !== undefined) {
|
|
896
725
|
throw new Error('mcc-match-codes 不接受 --agent-mcc-status 或 --agent-mcc-codes;MCC 匹配只使用本轮动态 catalog');
|
|
897
726
|
}
|
|
898
727
|
const records = recordsForArgs(args);
|
|
899
728
|
const agentReview = parseAgentCandidateRefs(args, records);
|
|
900
729
|
const mccRunContext = captureMccRunContext();
|
|
901
|
-
const result = resolveMccResultFromRecords(query, productType, records,
|
|
730
|
+
const result = resolveMccResultFromRecords(query, productType, records, undefined, mccRunContext, agentReview);
|
|
902
731
|
const { candidates: _internalCandidates, ...controlResult } = result;
|
|
903
732
|
process.stderr.write(`ALIPAY_AIPAY_INTERNAL:MCC_RESOLUTION=${JSON.stringify(controlResult)}\n`);
|
|
904
733
|
return '';
|
|
@@ -908,14 +737,15 @@ function runMccValidateSelectionCache(args) {
|
|
|
908
737
|
const productType = required(args, 'product-type');
|
|
909
738
|
productFor({ ...args, 'product-type': productType });
|
|
910
739
|
const records = recordsForArgs(args);
|
|
911
|
-
|
|
740
|
+
if (args['semantic-terms-json'] !== undefined) {
|
|
741
|
+
throw new Error('mcc-validate-selection-cache 不再接受 --semantic-terms-json');
|
|
742
|
+
}
|
|
912
743
|
const mccRunContext = captureMccRunContext();
|
|
913
744
|
if (!mccSelectionReceiptCodes(
|
|
914
745
|
productType,
|
|
915
746
|
required(args, 'query'),
|
|
916
747
|
required(args, 'selection-receipt'),
|
|
917
748
|
records,
|
|
918
|
-
semanticTerms,
|
|
919
749
|
mccRunContext
|
|
920
750
|
)) {
|
|
921
751
|
throw new Error('MCC 候选详情缓存与本轮选择回执不一致');
|
|
@@ -1578,8 +1578,8 @@ export function renderMessage(messageId, variantName, variables, telemetryContex
|
|
|
1578
1578
|
if (variables.overallResult === '未通过' && !hasFailedItems) {
|
|
1579
1579
|
throw new Error('代码开发结果为未通过时必须列出未通过项');
|
|
1580
1580
|
}
|
|
1581
|
-
if (variables.overallResult
|
|
1582
|
-
throw new Error('
|
|
1581
|
+
if (hasBlockingDefect && variables.overallResult !== '未通过') {
|
|
1582
|
+
throw new Error('存在代码、安全或必要依据阻塞缺口时,代码开发结果必须为未通过');
|
|
1583
1583
|
}
|
|
1584
1584
|
if (variables.overallResult !== '通过' && !sandboxPending && !hasBlockingDefect && !manualOnly) {
|
|
1585
1585
|
throw new Error('沙箱已就绪且没有阻塞缺口时,非通过结果只能来自人工待验证项');
|
|
@@ -1605,12 +1605,12 @@ export function renderMessage(messageId, variantName, variables, telemetryContex
|
|
|
1605
1605
|
if (variantName === 'WEBPAY' && variables.overallResult === '通过' && variables.webpayExperienceResult === '未提供浏览器付款入口或完整操作指引,需先补齐') {
|
|
1606
1606
|
throw new Error('AI 网页应用收款未交付付款入口时不得标记代码开发通过');
|
|
1607
1607
|
}
|
|
1608
|
-
if (manualOnly) {
|
|
1609
|
-
normalized.failedItems = `人工待验证项未完成:${normalized.manualItems}`;
|
|
1610
|
-
}
|
|
1611
1608
|
normalized.checklistDetails = variables.executionMode === '仅代码开发'
|
|
1612
1609
|
? ''
|
|
1613
|
-
:
|
|
1610
|
+
: [
|
|
1611
|
+
...(hasFailedItems ? [`未完成项:${normalized.failedItems}`] : []),
|
|
1612
|
+
...(hasManualItems ? [`人工待验证:${normalized.manualItems}`] : [])
|
|
1613
|
+
].join('\n');
|
|
1614
1614
|
const productName = { AIPAY: 'AI 按量付费', WEBPAY: 'AI 网页应用收款', APPPAY: 'AI 移动应用收款' }[variantName];
|
|
1615
1615
|
normalized.launchGuidance = variables.executionMode === '仅代码开发'
|
|
1616
1616
|
? productionReadinessBlock(productName, {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
步骤顺序固定为方案展示后自动授权,再执行适用只读查询,最后处理独立分支。查询失败只阻断对应分支,不当作空列表;其他成功分支可继续。
|
|
15
15
|
|
|
16
|
-
方案展示前只按本轮 `mcc.sh resolve-plan` 结果陈述 MCC
|
|
16
|
+
方案展示前只按本轮 `mcc.sh resolve-plan` 结果陈述 MCC 名称、描述和特殊资质。合法 MCC 编码和规范化后的完整类目名称由 runner 确定性匹配;其他自然语言由 Agent 对同一次 `queryMcc` 返回的当前产品完整 MCC 名称和描述做一次受限目录判断。Agent 只能返回目录引用,runner 再映射并校验为当前动态目录中的候选。`ar-support.queryMcc`、按需 `mccsearchservice.batchQueryMcc` 及候选快照的完整执行和失效规则见步骤 2;无查询依据的结论禁止推断。
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
@@ -103,29 +103,29 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message prod
|
|
|
103
103
|
|
|
104
104
|
产品明确但无经营描述/MCC:执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message mcc-clarify --product-type "$PRODUCT_TYPE" --root-intent "$ROOT_INTENT" --agent-platform "<AGENT_PLATFORM>" --session-id "<SESSION_ID>"`。该命令必须先创建或复用正式 telemetry run 并记录 `clarify_intent`;缺少或非法 `ROOT_INTENT` 时停止,不得继续 MCC 或授权。
|
|
105
105
|
|
|
106
|
-
已有经营描述、二级类目名称或编码时,以原话作为 `MCC_SEARCH_QUERY` 进入唯一 MCC
|
|
106
|
+
已有经营描述、二级类目名称或编码时,以原话作为 `MCC_SEARCH_QUERY` 进入唯一 MCC 解析/方案入口。合法 MCC 编码或规范化后与完整二级类目名称相等时由 runner 直接匹配,不调用 Agent。其他自然语言由 runner 输出一次 `MCC_AGENT_REVIEW_REQUIRED`,其中包含当前动态目录的稳定引用、名称、描述及固定判断约束;Agent 阅读用户完整原话和完整目录,只返回按匹配度排序的 0 到 8 个目录引用。用户明确提供的行业、商品、服务对象、经营方式和场景都是筛选条件,与明确条件冲突的候选必须排除;用户没有说明的细分维度保持开放,不能擅自排除或补充经营范围。候选超过 8 项时只保留最相关的 8 项。禁止业务词到 MCC 的硬编码点映射、自由提交 MCC code、远程语义或额外网络调用。
|
|
107
107
|
|
|
108
|
-
同一 `run +
|
|
108
|
+
同一 `run + 产品 + USER_INPUT_ID + 原始 MCC_SEARCH_QUERY + 动态目录摘要` 的自然语言判断只允许一次;同一 `USER_INPUT_ID` 不得通过改写 query 重新发起判断。Agent 返回非 JSON 字符串数组、目录外或重复引用、超过 8 项等协议错误时,runner 只输出一次 `MCC_AGENT_REPAIR_REQUIRED`;Agent 必须基于 marker 中的同一目录和明确错误原因,只修正格式、去重、排序或裁剪为最多 8 项,再用同一 query、`USER_INPUT_ID` 和 `--agent-candidate-refs-json` 原样重跑。修复不得重新解释用户意图、扩大经营范围、重新查询目录或改变用户原话。第二次仍非法、判断或修复状态重复、动态目录变化、状态不可安全记录时立即输出既有 MCC 暂时无法处理正文并停止,不回退字符匹配、不再次调用 Agent、不进入授权或签约。合法空数组表示当前目录无匹配,直接输出既有无匹配文案并等待用户补充描述,不触发修复。
|
|
109
109
|
|
|
110
|
-
`queryMcc` 树和多候选快照使用默认 1800 秒、本轮 run 私有缓存;无有效 run 不缓存。快照绑定产品、salesCode
|
|
110
|
+
`queryMcc` 树和多候选快照使用默认 1800 秒、本轮 run 私有缓存;无有效 run 不缓存。快照绑定产品、salesCode、原查询、选择回执、候选树事实及按需取得的资格详情;有效命中原样复用,失效才重查。两种 MCP 请求体不变;`batchQueryMcc` 最多一次,只传 Agent 最终候选中 `needSpecialQual=true` 的编码。
|
|
111
111
|
|
|
112
112
|
```bash
|
|
113
|
-
node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" mcc resolve-plan --query "$MCC_SEARCH_QUERY" --product-type "$PRODUCT_TYPE" --root-intent "$ROOT_INTENT" [--
|
|
113
|
+
node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" mcc resolve-plan --query "$MCC_SEARCH_QUERY" --product-type "$PRODUCT_TYPE" --root-intent "$ROOT_INTENT" [--agent-candidate-refs-json "$MCC_AGENT_CANDIDATE_REFS_JSON"] --input-id "$USER_INPUT_ID" [--telemetry-run-id "$RUN_ID" --telemetry-history-count "$HISTORY_COUNT"] --agent-platform "<AGENT_PLATFORM>" --session-id "<SESSION_ID>"
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
只收唯一 `ALIPAY_AIPAY_INTERNAL:MCC_QUERY_RESULT=SUCCESS`
|
|
116
|
+
只收唯一 `ALIPAY_AIPAY_INTERNAL:MCC_QUERY_RESULT=SUCCESS` 与适用的内部结果。若同时收到唯一 `MCC_AGENT_REVIEW_REQUIRED` 且 stdout 为空,Agent 按上面的判断约束返回 0 到 8 个当前目录引用并原样重跑一次;若收到唯一 `MCC_AGENT_REPAIR_REQUIRED` 且 stdout 为空,只做一次协议修复并原样重跑。两个 marker 和中间状态都不得展示给用户。`UNIQUE` 恰一项,并有唯一 `CUSTOMER_MESSAGE_DELIVERY_REQUIRED=onboarding.plan.confirm`、`MCC_CONTEXT_RECEIPT`;先把 stdout 作为可见进行中消息原文送达,随后同一回合进入 `execute`,不等待回复。`MULTIPLE` 送候选后等待;合法 `NONE` 送既有无匹配文案后等待用户重新描述。禁止旧 `mcc-search`/`mcc-candidates`、手写候选或近似追问。
|
|
117
117
|
|
|
118
118
|
`MCC_FLOW:RETRY_WITH_NETWORK`:联网后原样重试同一 `mcc resolve-plan`。`CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION` 且 stdout 空:给同命令 CLI 私有状态目录权限后原样重试。失败停 MCC;禁无匹配兜底或进方案/授权/签约。
|
|
119
119
|
|
|
120
|
-
`ALIPAY_AIPAY_INTERNAL:MCC_QUERY_RESULT=FAILED` 且无 `MCC_FLOW:RETRY_WITH_NETWORK`:送stdout,停MCC
|
|
120
|
+
`ALIPAY_AIPAY_INTERNAL:MCC_QUERY_RESULT=FAILED` 且无 `MCC_FLOW:RETRY_WITH_NETWORK`:送 stdout,停 MCC;非无匹配,禁候选/方案/授权/签约。只有用户按 stdout 指引重新发送经营描述后才开始新一轮匹配:先按统一前置规则计数,把该聊天消息原话作为新的 `MCC_SEARCH_QUERY`,使用该消息新的 `USER_INPUT_ID` 调用 `mcc resolve-plan`;不得复用上次的目录引用、修复 marker 或选择回执。即使用户原样重发经营描述,新 `USER_INPUT_ID` 也允许重新匹配。Agent 不得在没有新用户消息时自动发起第二轮,也不得因再次失败循环重试。
|
|
121
121
|
|
|
122
|
-
`MULTIPLE` 还须取得唯一 `MCC_SELECTION_RECEIPT`。用户回复序号、当前候选完整名称或编码时,以原始 `MCC_SEARCH_QUERY`
|
|
122
|
+
`MULTIPLE` 还须取得唯一 `MCC_SELECTION_RECEIPT`。用户回复序号、当前候选完整名称或编码时,以原始 `MCC_SEARCH_QUERY` 重跑同一入口:`node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" mcc resolve-plan --query "$MCC_SEARCH_QUERY" --product-type "$PRODUCT_TYPE" --root-intent "$ROOT_INTENT" --select "$SELECTED_MCC_CODE" --selection-receipt "$MCC_SELECTION_RECEIPT" --input-id "$USER_INPUT_ID" --telemetry-run-id "$RUN_ID" --telemetry-history-count "$HISTORY_COUNT" --agent-platform "<AGENT_PLATFORM>" --session-id "<SESSION_ID>"`。有效详情快照直接生成方案,不再调用 `queryMcc` 或 `batchQueryMcc`;runner 仍校验当前 run、产品、原话、候选和事实哈希。receipt 或快照失效时不得沿用或自动套用旧选择,必须基于完整当前产品目录重新判断并至少重新展示一次当前候选。
|
|
123
123
|
|
|
124
124
|
用户指定的 MCC 不在候选中时,按该 MCC 重新查询,不沿用旧候选回执,也不要要求用户换类目。
|
|
125
125
|
|
|
126
126
|
`salesCode`、`scope` 和基础材料只取“产品映射”。方案只由 `mcc resolve-plan` 或已有唯一 MCC 事实时的 `onboarding-message plan-confirm --root-intent "$ROOT_INTENT" --mcc-context-receipt ... [--telemetry-run-id "$RUN_ID" --telemetry-history-count "$HISTORY_COUNT"]` 生成,展示产品/scope、MCC、特殊资质、企业账号边界及商家平台链接。已有 active run 时必须传 telemetry 参数;如果该命令首次创建 run,则尚无 marker,省略参数并由同一进程把本次 plan stage 的 history 固定为 0,命令返回后取得后续使用的 `RUN_ID`。`ROOT_INTENT` 取当前路线根意图,只能是 `onboarding_only` 或 `full_process`。特殊资质只来自 `qualificationGroups`:多个 group 是 OR 关系;同一 group 内多个 qualifications 是 AND 关系;`attaType` 为空或 null 的 qualification 只展示 `qualName`,不生成上传项。禁止补充模板外资格或审核规则。
|
|
127
127
|
|
|
128
|
-
不得保留“先运行旧字符串搜索,无匹配后再临时让 Agent
|
|
128
|
+
不得保留“先运行旧字符串搜索,无匹配后再临时让 Agent 猜”的两阶段流程,或重复询问已知信息;自然语言只允许完整动态目录上的一次受限 Agent 判断和一次协议修复,二者预算由本轮私有状态原子固定。唯一事实校验通过、方案 marker 唯一且 stdout 已送达后,执行 `onboarding-execute-ready --product-type "$PRODUCT_TYPE"`;缺值/冲突/非唯一继续澄清,产品/MCC 变更重渲染。`execute` loader 只继承 `clarify_intent` 已记录的正式 run 事实,不再单独记录 stage,不代表新增用户回复,禁手工上报;Agent 在收到每条聊天框用户消息时,必须先按 `runtime-entry.md` 的统一前置命令计数,不按是否与 Skill 相关筛选,`summary` 收口后不再计入当前 run,`full_process` 不重置。
|
|
129
129
|
|
|
130
130
|
<!-- ALIPAY_AIPAY_FLOW:ONBOARDING_ENTRY:END -->
|
|
131
131
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
`queryMcc`、签约查询和签约提交使用 `{"request":{...},"ctx":{}}`;`batchQueryMcc` 使用 `{"request":{"mccCodes":[...]}}`。完整 payload 只在脚本中维护;Agent 不从本模块自行构造 MCP 调用。
|
|
15
15
|
|
|
16
|
-
`queryMcc` 成功树按同一 run、产品短时缓存,并逐项提供二级 MCC 编码、名称、描述和布尔型 `needSpecialQual
|
|
16
|
+
`queryMcc` 成功树按同一 run、产品短时缓存,并逐项提供二级 MCC 编码、名称、描述和布尔型 `needSpecialQual`;任一必需字段缺失、为空或类型异常均失败关闭,不从其他字段回退。合法 MCC 编码和规范化完整类目名称由 runner 确定性匹配;其他自然语言由 Agent 基于用户完整原话和同一次 `queryMcc` 返回的当前产品完整 MCC 名称、描述做一次受限判断,只返回按匹配度排序的最多 8 个稳定目录引用。用户明确约束用于排除冲突候选,未说明的维度保持开放。稳定引用由完整动态目录生成,并随候选裁剪和资格详情合并原样携带;最终方案 runner 不得按缩减后的候选数组重新编号。非法 Agent 结果只允许一次协议修复,第二次失败立即停止,不回退字符匹配或重复 Agent 判断。候选确定后,仅把其中 `needSpecialQual=true` 的编码集中交给一次 `batchQueryMcc` 获取特殊资质项;没有此类候选时不调用。多候选的完整树事实和资格详情按 run、产品、原查询和选择回执缓存,有效选择时两种 MCC 方法都不重调。缓存默认 1800 秒、无有效 run 不落盘;过期、损坏、符号链接、跨 run 或事实哈希不一致时忽略并从完整当前产品目录重新判断,不修补缓存事实,不做产品级全量预取。
|
|
17
17
|
|
|
18
18
|
## 签约状态
|
|
19
19
|
|