@alipay/alipay-aipay 1.8.3 → 1.8.5
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/dist/commands/install.js +10 -2
- package/dist/index.js +6 -1
- package/dist/utils/file.js +11 -2
- package/dist/utils/profile.js +64 -0
- package/package.json +3 -3
- package/skill/SKILL.md +2 -0
- package/skill/VERSION +1 -1
- package/skill/references/integration/flow.md +1 -1
- package/skill/references/integration/modules/sandbox/alipay-sandbox-tool.md +2 -2
- package/skill/references/normal/alipay-cli-env.md +2 -2
- package/skill/references/normal/scripts/common.sh +6 -2
- package/skill/references/normal/scripts/onboarding_message_runner.mjs +80 -250
- package/skill/references/normal/scripts/profile.mjs +53 -0
- package/skill/references/normal/scripts/session_skill.mjs +17 -2
- package/skill/references/normal/self-update.md +2 -0
- package/skill/references/onboarding/flow.md +11 -11
- 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/SKILL.md +2 -0
- package/skill-pre/VERSION +1 -1
- package/skill-pre/references/integration/flow.md +1 -1
- package/skill-pre/references/integration/modules/sandbox/alipay-sandbox-tool.md +2 -2
- package/skill-pre/references/normal/alipay-cli-env.md +2 -2
- package/skill-pre/references/normal/scripts/common.sh +6 -2
- package/skill-pre/references/normal/scripts/onboarding_message_runner.mjs +80 -250
- package/skill-pre/references/normal/scripts/profile.mjs +53 -0
- package/skill-pre/references/normal/scripts/session_skill.mjs +17 -2
- package/skill-pre/references/normal/self-update.md +2 -0
- package/skill-pre/references/onboarding/flow.md +11 -11
- 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
|
@@ -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 候选详情缓存与本轮选择回执不一致');
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const maxProfileBytes = 64 * 1024;
|
|
9
|
+
const validProfileValue = value => typeof value === 'string' && value.trim() === value
|
|
10
|
+
&& [...value].length >= 1 && [...value].length <= 128 && !/[\p{Cc}\p{Cs}]/u.test(value);
|
|
11
|
+
|
|
12
|
+
function readProfileFields(skillDir) {
|
|
13
|
+
try {
|
|
14
|
+
if (typeof skillDir !== 'string' || !path.isAbsolute(skillDir)) return {};
|
|
15
|
+
const file = path.join(skillDir, 'PROFILE');
|
|
16
|
+
const stat = fs.lstatSync(file);
|
|
17
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > maxProfileBytes) return {};
|
|
18
|
+
const profile = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
19
|
+
if (!profile || Array.isArray(profile) || profile.schemaVersion !== 1) return {};
|
|
20
|
+
return {
|
|
21
|
+
chinfo: validProfileValue(profile.chinfo) ? profile.chinfo : '',
|
|
22
|
+
vid: validProfileValue(profile.vid) ? profile.vid : '',
|
|
23
|
+
};
|
|
24
|
+
} catch {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function readProfileChinfo(skillDir) { return readProfileFields(skillDir).chinfo || ''; }
|
|
30
|
+
export function readProfileVid(skillDir) { return readProfileFields(skillDir).vid || ''; }
|
|
31
|
+
|
|
32
|
+
export function runProfileCli(argv = process.argv) {
|
|
33
|
+
if (argv.length === 5 && argv[2] === 'read-chinfo' && argv[3] === '--skill-dir') {
|
|
34
|
+
process.stdout.write(readProfileChinfo(argv[4]));
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
if (argv.length === 7 && argv[2] === 'merge-biz-info' && argv[3] === '--skill-dir' && argv[5] === '--base-json') {
|
|
38
|
+
try {
|
|
39
|
+
const base = JSON.parse(argv[6]);
|
|
40
|
+
if (!base || Array.isArray(base) || typeof base !== 'object') return 1;
|
|
41
|
+
const fields = readProfileFields(argv[4]);
|
|
42
|
+
if (fields.chinfo) base.chinfo = fields.chinfo;
|
|
43
|
+
if (fields.vid) base.vid = fields.vid;
|
|
44
|
+
process.stdout.write(JSON.stringify(base));
|
|
45
|
+
return 0;
|
|
46
|
+
} catch { return 1; }
|
|
47
|
+
}
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url))) {
|
|
52
|
+
process.exitCode = runProfileCli();
|
|
53
|
+
}
|
|
@@ -35,6 +35,7 @@ function filesUnder(root) {
|
|
|
35
35
|
else throw Error('Non-regular session Skill file');
|
|
36
36
|
};
|
|
37
37
|
for (const entry of ['SKILL.md','VERSION','references']) walk(entry);
|
|
38
|
+
if (fs.existsSync(path.join(root,'PROFILE'))) walk('PROFILE');
|
|
38
39
|
return files;
|
|
39
40
|
}
|
|
40
41
|
function identity(root, channel, version) {
|
|
@@ -48,15 +49,20 @@ function identity(root, channel, version) {
|
|
|
48
49
|
(channel==='pre'?'alipay-aipay-pre':'alipay-aipay')) throw Error('Session Skill identity mismatch');
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
export function createSessionSkill({sourceDir, channel, version, packageVersion, targetDir, home = os.homedir()}) {
|
|
52
|
+
export function createSessionSkill({sourceDir, channel, version, packageVersion, targetDir, profileContent, home = os.homedir()}) {
|
|
52
53
|
identity(sourceDir, channel, version);
|
|
53
54
|
if (!semver.test(packageVersion) || !path.isAbsolute(targetDir)) throw Error('Invalid session install source');
|
|
54
|
-
const manifest = filesUnder(sourceDir);
|
|
55
55
|
const root = privateRoot(home);
|
|
56
56
|
const skillDir = path.join(root, `${channel}-${version}-${randomUUID()}`);
|
|
57
57
|
fs.mkdirSync(skillDir, {mode:0o700});
|
|
58
58
|
try {
|
|
59
59
|
for (const entry of ['SKILL.md','VERSION','references']) fs.cpSync(path.join(sourceDir,entry),path.join(skillDir,entry),{recursive:true,errorOnExist:true,force:false});
|
|
60
|
+
const selectedProfile = profileContent === undefined ? readProfile(targetDir) : profileContent;
|
|
61
|
+
if (selectedProfile !== null && selectedProfile !== undefined) {
|
|
62
|
+
if (!(Buffer.isBuffer(selectedProfile) || selectedProfile instanceof Uint8Array) || selectedProfile.length > 64 * 1024) throw Error('Invalid PROFILE content');
|
|
63
|
+
fs.writeFileSync(path.join(skillDir,'PROFILE'),selectedProfile,{flag:'wx',mode:0o600});
|
|
64
|
+
}
|
|
65
|
+
const manifest = filesUnder(skillDir);
|
|
60
66
|
const receipt = {schemaVersion:1, scope:'session', channel, version, packageVersion,
|
|
61
67
|
skillDir:fs.realpathSync(skillDir), targetDir:path.resolve(targetDir)};
|
|
62
68
|
fs.writeFileSync(path.join(skillDir,receiptName),JSON.stringify({...receipt,manifest}),{flag:'wx',mode:0o600});
|
|
@@ -68,6 +74,15 @@ export function createSessionSkill({sourceDir, channel, version, packageVersion,
|
|
|
68
74
|
}
|
|
69
75
|
}
|
|
70
76
|
|
|
77
|
+
function readProfile(skillDir) {
|
|
78
|
+
const file=path.join(skillDir,'PROFILE');
|
|
79
|
+
if (!fs.existsSync(file)) return null;
|
|
80
|
+
plain(file);
|
|
81
|
+
const stat=fs.lstatSync(file);
|
|
82
|
+
if (stat.size > 64 * 1024) throw Error('PROFILE is too large');
|
|
83
|
+
return fs.readFileSync(file);
|
|
84
|
+
}
|
|
85
|
+
|
|
71
86
|
export function verifySessionSkill(receipt, {home = os.homedir(), channel, version, packageVersion, targetDir} = {}) {
|
|
72
87
|
if (!receipt || receipt.schemaVersion!==1 || receipt.scope!=='session' || !['stable','pre'].includes(receipt.channel) || !semver.test(receipt.version||'') || !path.isAbsolute(receipt.skillDir||'') ||
|
|
73
88
|
!path.isAbsolute(receipt.targetDir||'') || !semver.test(receipt.packageVersion||'')) throw Error('Invalid session receipt');
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
唯一 stderr 终态:`SELF_UPDATE:CURRENT=<合法 SemVer>`、`SELF_UPDATE:UPDATED=<合法 SemVer>`、`SELF_UPDATE:SESSION_READY=<合法 SemVer>`、`SELF_UPDATE:RETRY_WITH_NETWORK`、`SELF_UPDATE:SKIPPED_NETWORK_RESTRICTED`、`SELF_UPDATE:CHECK_FAILED`、`SELF_UPDATE:INSTALL_FAILED`、`SELF_UPDATE:VERIFY_FAILED`。`UPDATED` 用原子替换后的新版,除 `SESSION_READY` 外其余用当前可用版;marker 异常读当前入口,不得称最新或更新成功;停止读旧版,读取同一根目录的新版入口。仅 `RETRY_WITH_NETWORK` 可取权后原样重试一次,拒绝、不支持或再次返回时读当前入口;一次网络恢复外,不重查、取权、用 `sudo`、要求用户操作或写更新状态。每次只查一次 npm latest 的 `version` 和 `alipayAipayChannels`;只比较本渠道 Skill 版本,npm 包版本仅用于精确下载;`VERSION` 缺失、非法、落后或业务入口缺失时只从查询所得精确 npm 包版本安装当前渠道一次。渠道由当前目录的 `references/normal/json/release-channel.json` 与 Skill 名称共同校验;旧正式版缺少渠道文件时只兼容 `stable`,预发版禁止回退正式版。缺少或非法的远端渠道元数据返回 `CHECK_FAILED`,不得按 npm 包版本兜底。不调用公开 `install --target all`;超时终止同一次调用的进程树并清理。
|
|
6
6
|
|
|
7
|
+
公开 npm `install` 是 `PROFILE` 的唯一创建入口:`chinfo` 显式传值时更新为该来源,未传时更新为 `origin`;只有显式 `--vid <值>` 才新增或更新 `vid`,未传时保留已有 `vid`,其他已有扩展字段也保留。自更新只原样迁移当前 Skill 根目录已有的普通 `PROFILE`;不存在时新版及 Windows 会话副本都保持不存在。内部 `npx ... install-current` 不属于用户主动安装,禁止创建 `origin` 或修改 `vid`。源包的 `skill/` 与 `skill-pre/` 不包含 `PROFILE`。
|
|
8
|
+
|
|
7
9
|
本地正式渠道的 `X.Y.Z-beta`、`X.Y.Z-betaN`(兼容 `-beta.N`)和预发渠道的 `X.Y.Z-pre-beta`、`X.Y.Z-pre-betaN` 均为固定测试版本。完整安装直接返回 `CURRENT` 且不查询 npm;业务入口损坏时返回 `VERIFY_FAILED`,仍不下载修复或转正。更换版本必须由测试人员显式执行 npm 安装器的 `install`,普通 `update` 同样不推进 beta。正常版本分别为 `X.Y.Z` 与 `X.Y.Z-pre`,远端对应渠道若为 beta 或名称不匹配,返回 `CHECK_FAILED`,不自动安装。beta 不参与版本排序,无自动追踪和自动转正。
|
|
8
10
|
|
|
9
11
|
`SESSION_READY` 是 Windows 目录占用或权限失败的有限恢复:只有替换前的备份/暂存准备失败,安装器才从本次精确 npm 包创建并逐文件 SHA-256 复核独立副本;不混合覆盖原目录,不更新全局安装记录。必须同时取得唯一 `ALIPAY_AIPAY_INTERNAL:SELF_UPDATE_SESSION_SKILL_DIR=<JSON 字符串>`,用已核验的绝对路径重绑 `SKILL_DIR`,明确告知“全局目录未更新,当前会话使用已校验的新版本;下次会话仍可能加载旧版”。废弃旧 loaded/CLI 刷新事实,保留本渠道自更新已检查事实,立即读取新路径的 `SKILL.md` 并继续其余启动协议,禁止再次自更新。内测版仍需按新路径/版本完成声明及服务/采集选择,安装意图不得代替采集同意。路径 marker 缺失、冲突或新入口不可读时,不切换、不声称更新成功,使用仍可用的原目录或说明入口不可用。
|