@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
|
@@ -82,7 +82,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" telemetry resolve-sessi
|
|
|
82
82
|
|
|
83
83
|
每个登记 stage 动作前,Agent 回顾当前可见历史并得到 `HISTORY_COUNT`;只统计本 run `clarify_intent` 后的用户角色消息,无法可靠回顾时使用 `null`。调用能生成 stage 的业务命令时追加 `--telemetry-run-id "$RUN_ID" --telemetry-history-count "$HISTORY_COUNT"`,由统一 gateway 在业务 parser 前剥离并传给唯一 stage 责任方,Agent 不再额外调用 `telemetry stage`。首次在产品/MCC 确认命令内部新建 run 时尚无 marker,该进程使用刚创建的 active run 且本次 history 固定为 0。`c4` 只承载 `user_input_after_clarify_intent`、`history_count`、`root_intent` 和按唯一非 summary event 数计算的 `progress_pct`;正式计数始终取 runtime counter,history 只诊断、不纠偏。
|
|
84
84
|
|
|
85
|
-
三种入口均只在无法确定产品时执行 `product-clarify` runner;产品明确但缺 MCC 用 `mcc-clarify`,且必须携带当前 `ROOT_INTENT`,由 runner 先创建或复用正式 telemetry run。已有经营描述、名称或编码时只执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" mcc resolve-plan --query "$MCC_SEARCH_QUERY" --product-type "$PRODUCT_TYPE" --root-intent "$ROOT_INTENT" [--
|
|
85
|
+
三种入口均只在无法确定产品时执行 `product-clarify` runner;产品明确但缺 MCC 用 `mcc-clarify`,且必须携带当前 `ROOT_INTENT`,由 runner 先创建或复用正式 telemetry run。已有经营描述、名称或编码时只执行 `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" --agent-platform "<AGENT_PLATFORM>" --session-id "<SESSION_ID>"`。`ROOT_INTENT` 缺失或非法时 runner 必须停止,禁止依赖跨进程环境变量静默推断或在没有正式 run 的情况下继续 MCC 查询。合法 MCC 编码和规范化后的完整二级类目名称由 runner 直接匹配;其他自然语言只在同一次 `queryMcc` 返回的当前产品完整目录上触发一次受限 Agent 判断,Agent 根据用户完整原话返回按相关度排序的 0 到 8 个稳定目录引用。用户明确说明的行业、商品、服务对象、经营方式和场景必须作为约束;未说明的细分维度保持开放。非法结果只允许一次协议修复,修复不得重新解释用户意图;第二次非法、重复判断、结果变更、同一 `USER_INPUT_ID` 改写 query、动态目录变化或状态不可安全记录时立即停止,不回退字符匹配、不再次调用 Agent,也不进入授权或签约。首次合法结果与同一 `run + 产品 + USER_INPUT_ID + 原始 query + 动态目录摘要` 绑定;网络恢复时只允许同一结果幂等重放。目录判断和修复不新增 MCP、网络调用或用户确认。禁止业务词到 MCC 的硬编码映射、自由提交 MCC code、远程语义或手拼候选。runner 只为最终候选中 `needSpecialQual=true` 的 MCC 调用一次 `batchQueryMcc`,并复用有效的同 run/产品候选快照。
|
|
86
86
|
|
|
87
87
|
代码开发或完整接入缺项目位置/服务端语言时,使用 `integration.context.required` 或 `integration.project_path.required` 标准消息;框架缺失不触发补问。先区分新建/复用,`PROJECT_INPUT` 保留原话。Agent 确定两类项目的真实 `PROJECT_PATH`,以 `resolve-project --project-path "$PROJECT_PATH" --intent <existing|new>` 交 runner 校验;`PROJECT_INPUT` 不得代替路径。默认新建也先选安全路径并显式传入。现有项目位置未知时先定位候选;路径经 `resolve-project` 后再用 `framework-resolve`。补齐前不初始化、装依赖或改文件。
|
|
88
88
|
|
package/skill-pre/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.8.
|
|
1
|
+
1.8.3-pre
|
|
@@ -259,13 +259,6 @@ function assertMccQuery(query) {
|
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
const mccSelectionReceiptPrefix = 'AIPAY_MCC_SELECTION_V1.';
|
|
262
|
-
const ignoredMccQueryTokens = new Set([
|
|
263
|
-
'服务', '企业', '个人', '提供', '适用', '用于', '平台', '项目', '申请', '商家', '公司',
|
|
264
|
-
'互联', '联网', '互联网', '在线', '用品', '收费', '缴费', '费用', '适用于', '经营',
|
|
265
|
-
'业务', '相关', '主体', '从事', '网站', '网页', '购买', '买票', '购票', '票务', '售票',
|
|
266
|
-
'预约', '预定', '退票', '支付', '付款', '收款', '下单', '订单', '交易', '销售', '售卖',
|
|
267
|
-
'商品', '产品'
|
|
268
|
-
]);
|
|
269
262
|
|
|
270
263
|
function activeMccRunId(env = process.env) {
|
|
271
264
|
const candidate = env.ALIPAY_AIPAY_RUN_ID
|
|
@@ -397,8 +390,10 @@ function mccCatalogForAgent(records) {
|
|
|
397
390
|
}
|
|
398
391
|
|
|
399
392
|
function parseAgentCandidateRefs(args, records) {
|
|
400
|
-
|
|
401
|
-
|
|
393
|
+
if (!Object.prototype.hasOwnProperty.call(args, 'agent-candidate-refs-json')) {
|
|
394
|
+
return { provided: false, codes: [] };
|
|
395
|
+
}
|
|
396
|
+
const raw = args['agent-candidate-refs-json'];
|
|
402
397
|
let parsed;
|
|
403
398
|
try {
|
|
404
399
|
parsed = JSON.parse(raw);
|
|
@@ -416,7 +411,7 @@ function parseAgentCandidateRefs(args, records) {
|
|
|
416
411
|
}
|
|
417
412
|
codes.push(refs.get(ref));
|
|
418
413
|
}
|
|
419
|
-
return { codes };
|
|
414
|
+
return { provided: true, codes };
|
|
420
415
|
}
|
|
421
416
|
|
|
422
417
|
function mccTableSummary(records) {
|
|
@@ -454,190 +449,12 @@ function directMccCode(query, records) {
|
|
|
454
449
|
return { isCode: true, matches: [...records.keys()].filter((code) => code.endsWith(`_${raw}`)) };
|
|
455
450
|
}
|
|
456
451
|
|
|
457
|
-
function
|
|
458
|
-
const grams = new Set();
|
|
459
|
-
const searchableText = meaningfulMccSearch(value);
|
|
460
|
-
for (const segment of searchableText.split(/\s+/u).filter(Boolean)) {
|
|
461
|
-
const characters = [...segment];
|
|
462
|
-
for (let index = 0; index <= characters.length - size; index += 1) {
|
|
463
|
-
const gram = characters.slice(index, index + size).join('');
|
|
464
|
-
if (/^[\p{L}\p{N}]+$/u.test(gram)) grams.add(gram);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
return grams;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
function meaningfulMccSearch(value) {
|
|
471
|
-
let searchableText = normalizeSearch(value);
|
|
472
|
-
const ignoredTerms = [...ignoredMccQueryTokens]
|
|
473
|
-
.sort((left, right) => [...right].length - [...left].length);
|
|
474
|
-
for (const term of ignoredTerms) searchableText = searchableText.replaceAll(term, ' ');
|
|
475
|
-
return searchableText
|
|
476
|
-
.trim()
|
|
477
|
-
.replace(/\s+/gu, ' ')
|
|
478
|
-
.split(' ')
|
|
479
|
-
.filter((segment) => [...segment].length >= 2)
|
|
480
|
-
.join(' ');
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
function parseMccSemanticTerms(args) {
|
|
484
|
-
const raw = optional(args, 'semantic-terms-json');
|
|
485
|
-
if (raw === undefined) return [];
|
|
486
|
-
let parsed;
|
|
487
|
-
try {
|
|
488
|
-
parsed = JSON.parse(raw);
|
|
489
|
-
} catch {
|
|
490
|
-
throw new Error('semantic-terms-json 必须是合法 JSON 字符串数组');
|
|
491
|
-
}
|
|
492
|
-
if (!Array.isArray(parsed) || parsed.length > 12 || parsed.some((term) => typeof term !== 'string')) {
|
|
493
|
-
throw new Error('semantic-terms-json 必须是最多 12 项的 JSON 字符串数组');
|
|
494
|
-
}
|
|
495
|
-
const normalized = [];
|
|
496
|
-
let totalLength = 0;
|
|
497
|
-
for (const rawTerm of parsed) {
|
|
498
|
-
if (/\u0000|[\u0001-\u001f\u007f]/u.test(rawTerm)) {
|
|
499
|
-
throw new Error('semantic-terms-json 包含控制字符');
|
|
500
|
-
}
|
|
501
|
-
const term = normalizeSearch(rawTerm);
|
|
502
|
-
const termLength = [...term.replace(/\s+/gu, '')].length;
|
|
503
|
-
if (termLength < 2 || termLength > 40) {
|
|
504
|
-
throw new Error('semantic-terms-json 每项长度必须为 2 到 40 个字符');
|
|
505
|
-
}
|
|
506
|
-
if (/(?:A\d{4}\s*)?B\d{4}/iu.test(term)) {
|
|
507
|
-
throw new Error('semantic-terms-json 只能包含业务概念词,禁止提供 MCC 编码');
|
|
508
|
-
}
|
|
509
|
-
totalLength += termLength;
|
|
510
|
-
if (!normalized.includes(term)) normalized.push(term);
|
|
511
|
-
}
|
|
512
|
-
if (totalLength > 240) throw new Error('semantic-terms-json 总长度不得超过 240 个字符');
|
|
513
|
-
return normalized.sort((left, right) => (left < right ? -1 : left > right ? 1 : 0));
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
function mccDocumentFrequency(indexedRecords) {
|
|
517
|
-
const frequencies = new Map();
|
|
518
|
-
for (const indexed of indexedRecords) {
|
|
519
|
-
const documentGrams = new Set([
|
|
520
|
-
...indexed.nameBigrams,
|
|
521
|
-
...indexed.nameTrigrams,
|
|
522
|
-
...indexed.businessBigrams,
|
|
523
|
-
...indexed.businessTrigrams
|
|
524
|
-
]);
|
|
525
|
-
for (const gram of documentGrams) frequencies.set(gram, (frequencies.get(gram) || 0) + 1);
|
|
526
|
-
}
|
|
527
|
-
return frequencies;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
function weightedMccOverlap(queryGrams, documentGrams, frequencies, recordCount, size) {
|
|
531
|
-
let weight = 0;
|
|
532
|
-
for (const gram of queryGrams) {
|
|
533
|
-
if (!documentGrams.has(gram)) continue;
|
|
534
|
-
const documentCount = frequencies.get(gram) || 0;
|
|
535
|
-
weight += (Math.log((recordCount + 1) / (documentCount + 1)) + 1) * (size === 3 ? 1.6 : 1);
|
|
536
|
-
}
|
|
537
|
-
return weight;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
function mccPhraseCoverage(haystackSegments, needleSegments) {
|
|
541
|
-
let coverage = 0;
|
|
542
|
-
for (const needle of needleSegments) {
|
|
543
|
-
if (ignoredMccQueryTokens.has(needle)) continue;
|
|
544
|
-
const needleLength = [...needle].length;
|
|
545
|
-
for (const haystack of haystackSegments) {
|
|
546
|
-
if (!haystack.includes(needle)) continue;
|
|
547
|
-
coverage = Math.max(coverage, needleLength / [...haystack].length);
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
return coverage;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
function mccTextEvidence(value, indexed, frequencies, recordCount) {
|
|
554
|
-
const normalizedValue = normalizeSearch(value);
|
|
555
|
-
const valueSegments = meaningfulMccSearch(normalizedValue).split(' ').filter(Boolean);
|
|
556
|
-
const valueBigrams = mccNgrams(normalizedValue, 2);
|
|
557
|
-
const valueTrigrams = mccNgrams(normalizedValue, 3);
|
|
558
|
-
const exactName = indexed.name === normalizedValue
|
|
559
|
-
|| (normalizedValue.split(' ').length === 1 && indexed.nameSegments.includes(normalizedValue));
|
|
560
|
-
const nameContainsValueCoverage = mccPhraseCoverage(indexed.nameEvidenceSegments, valueSegments);
|
|
561
|
-
const valueContainsNameCoverage = mccPhraseCoverage(valueSegments, indexed.nameEvidenceSegments);
|
|
562
|
-
const businessContainsValueCoverage = mccPhraseCoverage(indexed.businessEvidenceSegments, valueSegments);
|
|
563
|
-
const nameBigram = weightedMccOverlap(valueBigrams, indexed.nameBigrams, frequencies, recordCount, 2);
|
|
564
|
-
const nameTrigram = weightedMccOverlap(valueTrigrams, indexed.nameTrigrams, frequencies, recordCount, 3);
|
|
565
|
-
const businessBigram = weightedMccOverlap(valueBigrams, indexed.businessBigrams, frequencies, recordCount, 2);
|
|
566
|
-
const businessTrigram = weightedMccOverlap(valueTrigrams, indexed.businessTrigrams, frequencies, recordCount, 3);
|
|
567
|
-
const matchedBigrams = new Set([
|
|
568
|
-
...[...valueBigrams].filter((gram) => indexed.nameBigrams.has(gram)),
|
|
569
|
-
...[...valueBigrams].filter((gram) => indexed.businessBigrams.has(gram))
|
|
570
|
-
]);
|
|
571
|
-
const matchedTrigrams = new Set([
|
|
572
|
-
...[...valueTrigrams].filter((gram) => indexed.nameTrigrams.has(gram)),
|
|
573
|
-
...[...valueTrigrams].filter((gram) => indexed.businessTrigrams.has(gram))
|
|
574
|
-
]);
|
|
575
|
-
const rareBigramDocumentLimit = Math.max(1, Math.ceil(recordCount * 0.02));
|
|
576
|
-
const hasRareBigram = [...matchedBigrams].some((gram) => (
|
|
577
|
-
(frequencies.get(gram) || recordCount) <= rareBigramDocumentLimit
|
|
578
|
-
));
|
|
579
|
-
const hasLexicalEvidence = matchedTrigrams.size > 0
|
|
580
|
-
|| matchedBigrams.size >= 2
|
|
581
|
-
|| (valueBigrams.size <= 2 && hasRareBigram);
|
|
582
|
-
const phraseEvidence = nameContainsValueCoverage > 0
|
|
583
|
-
|| valueContainsNameCoverage > 0
|
|
584
|
-
|| businessContainsValueCoverage > 0;
|
|
585
|
-
return {
|
|
586
|
-
eligible: exactName || phraseEvidence || hasLexicalEvidence,
|
|
587
|
-
exactName,
|
|
588
|
-
score: (exactName ? 100000 : 0)
|
|
589
|
-
+ (nameContainsValueCoverage * 6000)
|
|
590
|
-
+ (valueContainsNameCoverage * 3000)
|
|
591
|
-
+ (businessContainsValueCoverage * 2500)
|
|
592
|
-
+ ((nameBigram + nameTrigram) * 120)
|
|
593
|
-
+ ((businessBigram + businessTrigram) * 40)
|
|
594
|
-
};
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
function deterministicMccMatches(query, records, { limit = 8, semanticTerms = [] } = {}) {
|
|
452
|
+
function directMccName(query, records) {
|
|
598
453
|
const normalizedQuery = assertMccQuery(query);
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
record,
|
|
604
|
-
name,
|
|
605
|
-
nameSegments: name.split(' ').filter(Boolean),
|
|
606
|
-
nameEvidenceSegments: meaningfulMccSearch(name).split(' ').filter(Boolean),
|
|
607
|
-
businessEvidenceSegments: meaningfulMccSearch(businesses).split(' ').filter(Boolean),
|
|
608
|
-
nameBigrams: mccNgrams(name, 2),
|
|
609
|
-
nameTrigrams: mccNgrams(name, 3),
|
|
610
|
-
businessBigrams: mccNgrams(businesses, 2),
|
|
611
|
-
businessTrigrams: mccNgrams(businesses, 3)
|
|
612
|
-
};
|
|
613
|
-
});
|
|
614
|
-
const frequencies = mccDocumentFrequency(indexedRecords);
|
|
615
|
-
const scored = [];
|
|
616
|
-
|
|
617
|
-
for (const indexed of indexedRecords) {
|
|
618
|
-
const originalEvidence = mccTextEvidence(normalizedQuery, indexed, frequencies, indexedRecords.length);
|
|
619
|
-
const semanticEvidence = semanticTerms
|
|
620
|
-
.map((term) => mccTextEvidence(term, indexed, frequencies, indexedRecords.length))
|
|
621
|
-
.filter((evidence) => evidence.eligible)
|
|
622
|
-
.sort((left, right) => right.score - left.score)[0];
|
|
623
|
-
if (!originalEvidence.eligible && !semanticEvidence) continue;
|
|
624
|
-
scored.push({
|
|
625
|
-
record: indexed.record,
|
|
626
|
-
score: originalEvidence.score + ((semanticEvidence?.score || 0) * 0.7),
|
|
627
|
-
exactName: originalEvidence.exactName,
|
|
628
|
-
originalEligible: originalEvidence.eligible,
|
|
629
|
-
semanticEligible: Boolean(semanticEvidence),
|
|
630
|
-
semanticExactName: Boolean(semanticEvidence?.exactName)
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
const exactMatches = scored.filter(({ exactName }) => exactName);
|
|
635
|
-
const ranked = exactMatches.length > 0
|
|
636
|
-
? exactMatches
|
|
637
|
-
: scored;
|
|
638
|
-
return ranked
|
|
639
|
-
.sort((left, right) => right.score - left.score || left.record.code.localeCompare(right.record.code))
|
|
640
|
-
.slice(0, limit);
|
|
454
|
+
return sortedMccRecords(records)
|
|
455
|
+
.filter((record) => normalizeSearch(record.name) === normalizedQuery)
|
|
456
|
+
.map((record) => record.code)
|
|
457
|
+
.slice(0, 8);
|
|
641
458
|
}
|
|
642
459
|
|
|
643
460
|
function createMccSelectionReceipt(productType, query, result, mccRunContext = captureMccRunContext()) {
|
|
@@ -647,7 +464,6 @@ function createMccSelectionReceipt(productType, query, result, mccRunContext = c
|
|
|
647
464
|
mccRunContext.runId,
|
|
648
465
|
productType,
|
|
649
466
|
assertMccQuery(query),
|
|
650
|
-
JSON.stringify(result.semanticTerms || []),
|
|
651
467
|
table.sha256,
|
|
652
468
|
table.recordCount,
|
|
653
469
|
codes
|
|
@@ -655,8 +471,8 @@ function createMccSelectionReceipt(productType, query, result, mccRunContext = c
|
|
|
655
471
|
return `${mccSelectionReceiptPrefix}${codes}.${createHash('sha256').update(binding).digest('base64url')}`;
|
|
656
472
|
}
|
|
657
473
|
|
|
658
|
-
function resolveMccSelection(productType, query, selectedInput, receipt, records,
|
|
659
|
-
const candidateCodes = mccSelectionReceiptCodes(productType, query, receipt, records,
|
|
474
|
+
function resolveMccSelection(productType, query, selectedInput, receipt, records, mccRunContext) {
|
|
475
|
+
const candidateCodes = mccSelectionReceiptCodes(productType, query, receipt, records, mccRunContext);
|
|
660
476
|
if (!candidateCodes) {
|
|
661
477
|
const semanticStatus = typeof receipt === 'string' && receipt.startsWith(mccSelectionReceiptPrefix)
|
|
662
478
|
? 'SELECTION_RECEIPT_STALE'
|
|
@@ -665,10 +481,8 @@ function resolveMccSelection(productType, query, selectedInput, receipt, records
|
|
|
665
481
|
query,
|
|
666
482
|
productType,
|
|
667
483
|
records,
|
|
668
|
-
semanticTerms,
|
|
669
484
|
semanticStatus,
|
|
670
|
-
mccRunContext
|
|
671
|
-
agentCandidateCodes
|
|
485
|
+
mccRunContext
|
|
672
486
|
);
|
|
673
487
|
return {
|
|
674
488
|
...refreshed,
|
|
@@ -685,8 +499,7 @@ function resolveMccSelection(productType, query, selectedInput, receipt, records
|
|
|
685
499
|
deterministicCandidateCodes: [],
|
|
686
500
|
deterministicStrongCandidateCodes: [],
|
|
687
501
|
deterministicWeakCandidateCodes: [],
|
|
688
|
-
|
|
689
|
-
semanticTerms,
|
|
502
|
+
agentCandidateCodes: [],
|
|
690
503
|
mergedCandidateCodes: candidateCodes,
|
|
691
504
|
mccTableSummary: currentTable,
|
|
692
505
|
candidates: candidateCodes.map((code) => mccCandidateFromRecord(records.get(code), productType, mccRunContext))
|
|
@@ -699,15 +512,14 @@ function resolveMccSelection(productType, query, selectedInput, receipt, records
|
|
|
699
512
|
deterministicCandidateCodes: [],
|
|
700
513
|
deterministicStrongCandidateCodes: [],
|
|
701
514
|
deterministicWeakCandidateCodes: [],
|
|
702
|
-
|
|
703
|
-
semanticTerms,
|
|
515
|
+
agentCandidateCodes: [],
|
|
704
516
|
mergedCandidateCodes: [selectedCode],
|
|
705
517
|
mccTableSummary: currentTable,
|
|
706
518
|
candidates: [candidate]
|
|
707
519
|
};
|
|
708
520
|
}
|
|
709
521
|
|
|
710
|
-
function mccSelectionReceiptCodes(productType, query, receipt, records,
|
|
522
|
+
function mccSelectionReceiptCodes(productType, query, receipt, records, mccRunContext = captureMccRunContext()) {
|
|
711
523
|
if (typeof receipt !== 'string' || !receipt.startsWith(mccSelectionReceiptPrefix)) return null;
|
|
712
524
|
const body = receipt.slice(mccSelectionReceiptPrefix.length);
|
|
713
525
|
const separator = body.lastIndexOf('.');
|
|
@@ -718,8 +530,7 @@ function mccSelectionReceiptCodes(productType, query, receipt, records, semantic
|
|
|
718
530
|
|| candidateCodes.some((code) => !records.has(code))
|
|
719
531
|
|| receipt !== createMccSelectionReceipt(productType, query, {
|
|
720
532
|
mergedCandidateCodes: candidateCodes,
|
|
721
|
-
mccTableSummary: currentTable
|
|
722
|
-
semanticTerms
|
|
533
|
+
mccTableSummary: currentTable
|
|
723
534
|
}, mccRunContext)) {
|
|
724
535
|
return null;
|
|
725
536
|
}
|
|
@@ -730,10 +541,9 @@ function resolveMccResultFromRecords(
|
|
|
730
541
|
query,
|
|
731
542
|
productType,
|
|
732
543
|
records,
|
|
733
|
-
semanticTerms = [],
|
|
734
544
|
semanticStatus,
|
|
735
545
|
mccRunContext = captureMccRunContext(),
|
|
736
|
-
|
|
546
|
+
agentReview = { provided: false, codes: [] }
|
|
737
547
|
) {
|
|
738
548
|
const directCode = directMccCode(query, records);
|
|
739
549
|
if (directCode.isCode) {
|
|
@@ -746,9 +556,7 @@ function resolveMccResultFromRecords(
|
|
|
746
556
|
deterministicCandidateCodes: [code],
|
|
747
557
|
deterministicStrongCandidateCodes: [code],
|
|
748
558
|
deterministicWeakCandidateCodes: [],
|
|
749
|
-
semanticCandidateCodes: [],
|
|
750
559
|
agentCandidateCodes: [],
|
|
751
|
-
semanticTerms,
|
|
752
560
|
mergedCandidateCodes: [code],
|
|
753
561
|
mccTableSummary: mccTableSummary(records),
|
|
754
562
|
candidates: [candidate]
|
|
@@ -761,52 +569,69 @@ function resolveMccResultFromRecords(
|
|
|
761
569
|
deterministicCandidateCodes: finalCodes,
|
|
762
570
|
deterministicStrongCandidateCodes: finalCodes,
|
|
763
571
|
deterministicWeakCandidateCodes: [],
|
|
764
|
-
semanticCandidateCodes: [],
|
|
765
572
|
agentCandidateCodes: [],
|
|
766
|
-
semanticTerms,
|
|
767
573
|
mergedCandidateCodes: finalCodes,
|
|
768
574
|
mccTableSummary: mccTableSummary(records),
|
|
769
575
|
candidates: finalCodes.map((code) => mccCandidateFromRecord(records.get(code), productType, mccRunContext))
|
|
770
576
|
};
|
|
771
577
|
}
|
|
772
578
|
|
|
773
|
-
const
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
579
|
+
const exactNameCodes = directMccName(query, records);
|
|
580
|
+
if (exactNameCodes.length > 0) {
|
|
581
|
+
const candidates = exactNameCodes.map((code) => mccCandidateFromRecord(records.get(code), productType, mccRunContext));
|
|
582
|
+
return {
|
|
583
|
+
matchType: candidates.length === 1 ? 'UNIQUE' : 'MULTIPLE',
|
|
584
|
+
semanticStatus: 'NOT_REQUIRED_EXACT_NAME',
|
|
585
|
+
deterministicCandidateCodes: exactNameCodes,
|
|
586
|
+
deterministicStrongCandidateCodes: exactNameCodes,
|
|
587
|
+
deterministicWeakCandidateCodes: [],
|
|
588
|
+
agentCandidateCodes: [],
|
|
589
|
+
mergedCandidateCodes: exactNameCodes,
|
|
590
|
+
mccTableSummary: mccTableSummary(records),
|
|
591
|
+
candidates
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (!agentReview.provided) {
|
|
596
|
+
return {
|
|
597
|
+
matchType: 'NONE',
|
|
598
|
+
semanticStatus: semanticStatus || 'AGENT_CATALOG_REVIEW_REQUIRED',
|
|
599
|
+
deterministicCandidateCodes: [],
|
|
600
|
+
deterministicStrongCandidateCodes: [],
|
|
601
|
+
deterministicWeakCandidateCodes: [],
|
|
602
|
+
agentCandidateCodes: [],
|
|
603
|
+
mergedCandidateCodes: [],
|
|
604
|
+
mccTableSummary: mccTableSummary(records),
|
|
605
|
+
candidates: [],
|
|
606
|
+
agentReview: {
|
|
607
|
+
required: true,
|
|
608
|
+
attempt: 1,
|
|
609
|
+
maxAttempts: 2,
|
|
610
|
+
reason: 'NATURAL_LANGUAGE_MATCH',
|
|
611
|
+
instructions: {
|
|
612
|
+
keepExplicitConstraints: true,
|
|
613
|
+
keepUnspecifiedDimensionsOpen: true,
|
|
614
|
+
excludeContradictions: true,
|
|
615
|
+
rankByRelevance: true,
|
|
616
|
+
maxCandidates: 8
|
|
617
|
+
},
|
|
618
|
+
catalog: mccCatalogForAgent(records)
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const finalCodes = agentReview.codes;
|
|
781
624
|
const candidates = finalCodes.map((code) => mccCandidateFromRecord(records.get(code), productType, mccRunContext));
|
|
782
|
-
const needsAgentReview = agentCandidateCodes.length === 0
|
|
783
|
-
&& (
|
|
784
|
-
matches.length === 0
|
|
785
|
-
|| deterministicCodes.length !== 1
|
|
786
|
-
|| (semanticTerms.length > 0 && deterministicCodes.length === 0 && !hasExactSemanticName)
|
|
787
|
-
);
|
|
788
625
|
return {
|
|
789
626
|
matchType: candidates.length === 1 ? 'UNIQUE' : candidates.length === 0 ? 'NONE' : 'MULTIPLE',
|
|
790
|
-
semanticStatus: semanticStatus
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
deterministicStrongCandidateCodes: deterministicCodes,
|
|
627
|
+
semanticStatus: semanticStatus || 'AGENT_CATALOG_REVIEW_USED',
|
|
628
|
+
deterministicCandidateCodes: [],
|
|
629
|
+
deterministicStrongCandidateCodes: [],
|
|
794
630
|
deterministicWeakCandidateCodes: [],
|
|
795
|
-
|
|
796
|
-
agentCandidateCodes,
|
|
797
|
-
semanticTerms,
|
|
631
|
+
agentCandidateCodes: finalCodes,
|
|
798
632
|
mergedCandidateCodes: finalCodes,
|
|
799
633
|
mccTableSummary: mccTableSummary(records),
|
|
800
|
-
candidates
|
|
801
|
-
...(needsAgentReview ? {
|
|
802
|
-
agentReview: {
|
|
803
|
-
required: true,
|
|
804
|
-
attempt: 1,
|
|
805
|
-
maxAttempts: 1,
|
|
806
|
-
reason: 'LOW_CONFIDENCE_LOCAL_MATCH',
|
|
807
|
-
catalog: mccCatalogForAgent(records)
|
|
808
|
-
}
|
|
809
|
-
} : {})
|
|
634
|
+
candidates
|
|
810
635
|
};
|
|
811
636
|
}
|
|
812
637
|
|
|
@@ -856,7 +681,9 @@ async function runMccResolvePlan(args) {
|
|
|
856
681
|
const selected = optional(args, 'select');
|
|
857
682
|
const selectionReceipt = optional(args, 'selection-receipt');
|
|
858
683
|
const selectionState = optional(args, 'selection-state');
|
|
859
|
-
|
|
684
|
+
if (args['semantic-terms-json'] !== undefined) {
|
|
685
|
+
throw new Error('mcc-resolve-plan 不再接受 --semantic-terms-json;自然语言只使用一次受限目录判断');
|
|
686
|
+
}
|
|
860
687
|
if ((selected === undefined) !== (selectionReceipt === undefined)) {
|
|
861
688
|
throw new Error('--select 与 --selection-receipt 必须同时提供');
|
|
862
689
|
}
|
|
@@ -872,8 +699,8 @@ async function runMccResolvePlan(args) {
|
|
|
872
699
|
const records = recordsForArgs(args);
|
|
873
700
|
const agentReview = parseAgentCandidateRefs(args, records);
|
|
874
701
|
let result = selected === undefined
|
|
875
|
-
? resolveMccResultFromRecords(query, productType, records,
|
|
876
|
-
: resolveMccSelection(productType, query, selected, selectionReceipt, records,
|
|
702
|
+
? resolveMccResultFromRecords(query, productType, records, undefined, mccRunContext, agentReview)
|
|
703
|
+
: resolveMccSelection(productType, query, selected, selectionReceipt, records, mccRunContext);
|
|
877
704
|
if (selectionState === 'STALE') {
|
|
878
705
|
result = {
|
|
879
706
|
...result,
|
|
@@ -910,14 +737,16 @@ async function runMccMatchCodes(args) {
|
|
|
910
737
|
}
|
|
911
738
|
await startTelemetryRun(rootIntent, productType);
|
|
912
739
|
const query = required(args, 'query');
|
|
913
|
-
|
|
740
|
+
if (args['semantic-terms-json'] !== undefined) {
|
|
741
|
+
throw new Error('mcc-match-codes 不再接受 --semantic-terms-json;自然语言只使用一次受限目录判断');
|
|
742
|
+
}
|
|
914
743
|
if (args['agent-mcc-status'] !== undefined || args['agent-mcc-codes'] !== undefined) {
|
|
915
744
|
throw new Error('mcc-match-codes 不接受 --agent-mcc-status 或 --agent-mcc-codes;MCC 匹配只使用本轮动态 catalog');
|
|
916
745
|
}
|
|
917
746
|
const records = recordsForArgs(args);
|
|
918
747
|
const agentReview = parseAgentCandidateRefs(args, records);
|
|
919
748
|
const mccRunContext = captureMccRunContext();
|
|
920
|
-
const result = resolveMccResultFromRecords(query, productType, records,
|
|
749
|
+
const result = resolveMccResultFromRecords(query, productType, records, undefined, mccRunContext, agentReview);
|
|
921
750
|
const { candidates: _internalCandidates, ...controlResult } = result;
|
|
922
751
|
process.stderr.write(`ALIPAY_AIPAY_INTERNAL:MCC_RESOLUTION=${JSON.stringify(controlResult)}\n`);
|
|
923
752
|
return '';
|
|
@@ -927,14 +756,15 @@ function runMccValidateSelectionCache(args) {
|
|
|
927
756
|
const productType = required(args, 'product-type');
|
|
928
757
|
productFor({ ...args, 'product-type': productType });
|
|
929
758
|
const records = recordsForArgs(args);
|
|
930
|
-
|
|
759
|
+
if (args['semantic-terms-json'] !== undefined) {
|
|
760
|
+
throw new Error('mcc-validate-selection-cache 不再接受 --semantic-terms-json');
|
|
761
|
+
}
|
|
931
762
|
const mccRunContext = captureMccRunContext();
|
|
932
763
|
if (!mccSelectionReceiptCodes(
|
|
933
764
|
productType,
|
|
934
765
|
required(args, 'query'),
|
|
935
766
|
required(args, 'selection-receipt'),
|
|
936
767
|
records,
|
|
937
|
-
semanticTerms,
|
|
938
768
|
mccRunContext
|
|
939
769
|
)) {
|
|
940
770
|
throw new Error('MCC 候选详情缓存与本轮选择回执不一致');
|
|
@@ -17,7 +17,7 @@ Windows 的 READY/LEGACY 取根入口唯一分流,或 Windows 环境文档明
|
|
|
17
17
|
|
|
18
18
|
步骤顺序固定为方案展示后自动授权,再执行适用只读查询,最后处理独立分支。查询失败只阻断对应分支,不当作空列表;其他成功分支可继续。
|
|
19
19
|
|
|
20
|
-
方案展示前只按本轮 `mcc.sh resolve-plan` 结果陈述 MCC
|
|
20
|
+
方案展示前只按本轮 `mcc.sh resolve-plan` 结果陈述 MCC 名称、描述和特殊资质。合法 MCC 编码和规范化后的完整类目名称由 runner 确定性匹配;其他自然语言由 Agent 对同一次 `queryMcc` 返回的当前产品完整 MCC 名称和描述做一次受限目录判断。Agent 只能返回目录引用,runner 再映射并校验为当前动态目录中的候选。`ar-support.queryMcc`、按需 `mccsearchservice.batchQueryMcc` 及候选快照的完整执行和失效规则见步骤 2;无查询依据的结论禁止推断。
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
@@ -107,29 +107,29 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message prod
|
|
|
107
107
|
|
|
108
108
|
产品明确但无经营描述/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 或授权。
|
|
109
109
|
|
|
110
|
-
已有经营描述、二级类目名称或编码时,以原话作为 `MCC_SEARCH_QUERY` 进入唯一 MCC
|
|
110
|
+
已有经营描述、二级类目名称或编码时,以原话作为 `MCC_SEARCH_QUERY` 进入唯一 MCC 解析/方案入口。合法 MCC 编码或规范化后与完整二级类目名称相等时由 runner 直接匹配,不调用 Agent。其他自然语言由 runner 输出一次 `MCC_AGENT_REVIEW_REQUIRED`,其中包含当前动态目录的稳定引用、名称、描述及固定判断约束;Agent 阅读用户完整原话和完整目录,只返回按匹配度排序的 0 到 8 个目录引用。用户明确提供的行业、商品、服务对象、经营方式和场景都是筛选条件,与明确条件冲突的候选必须排除;用户没有说明的细分维度保持开放,不能擅自排除或补充经营范围。候选超过 8 项时只保留最相关的 8 项。禁止业务词到 MCC 的硬编码点映射、自由提交 MCC code、远程语义或额外网络调用。
|
|
111
111
|
|
|
112
|
-
同一 `run +
|
|
112
|
+
同一 `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、不进入授权或签约。合法空数组表示当前目录无匹配,直接输出既有无匹配文案并等待用户补充描述,不触发修复。
|
|
113
113
|
|
|
114
|
-
`queryMcc` 树和多候选快照使用默认 1800 秒、本轮 run 私有缓存;无有效 run 不缓存。快照绑定产品、salesCode
|
|
114
|
+
`queryMcc` 树和多候选快照使用默认 1800 秒、本轮 run 私有缓存;无有效 run 不缓存。快照绑定产品、salesCode、原查询、选择回执、候选树事实及按需取得的资格详情;有效命中原样复用,失效才重查。两种 MCP 请求体不变;`batchQueryMcc` 最多一次,只传 Agent 最终候选中 `needSpecialQual=true` 的编码。
|
|
115
115
|
|
|
116
116
|
```bash
|
|
117
|
-
node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" mcc resolve-plan --query "$MCC_SEARCH_QUERY" --product-type "$PRODUCT_TYPE" --root-intent "$ROOT_INTENT" [--
|
|
117
|
+
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>"
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
只收唯一 `ALIPAY_AIPAY_INTERNAL:MCC_QUERY_RESULT=SUCCESS`
|
|
120
|
+
只收唯一 `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`、手写候选或近似追问。
|
|
121
121
|
|
|
122
122
|
`MCC_FLOW:RETRY_WITH_NETWORK`:联网后原样重试同一 `mcc resolve-plan`。`CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION` 且 stdout 空:给同命令 CLI 私有状态目录权限后原样重试。失败停 MCC;禁无匹配兜底或进方案/授权/签约。
|
|
123
123
|
|
|
124
|
-
`ALIPAY_AIPAY_INTERNAL:MCC_QUERY_RESULT=FAILED` 且无 `MCC_FLOW:RETRY_WITH_NETWORK`:送stdout,停MCC
|
|
124
|
+
`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 不得在没有新用户消息时自动发起第二轮,也不得因再次失败循环重试。
|
|
125
125
|
|
|
126
|
-
`MULTIPLE` 还须取得唯一 `MCC_SELECTION_RECEIPT`。用户回复序号、当前候选完整名称或编码时,以原始 `MCC_SEARCH_QUERY`
|
|
126
|
+
`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 或快照失效时不得沿用或自动套用旧选择,必须基于完整当前产品目录重新判断并至少重新展示一次当前候选。
|
|
127
127
|
|
|
128
128
|
用户指定的 MCC 不在候选中时,按该 MCC 重新查询,不沿用旧候选回执,也不要要求用户换类目。
|
|
129
129
|
|
|
130
130
|
`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`,不生成上传项。禁止补充模板外资格或审核规则。
|
|
131
131
|
|
|
132
|
-
不得保留“先运行旧字符串搜索,无匹配后再临时让 Agent
|
|
132
|
+
不得保留“先运行旧字符串搜索,无匹配后再临时让 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` 不重置。
|
|
133
133
|
|
|
134
134
|
<!-- ALIPAY_AIPAY_FLOW:ONBOARDING_ENTRY:END -->
|
|
135
135
|
|
|
@@ -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
|
|