@lqc123qwe/car-runtime 1.22.1 → 1.24.0
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/SHA256SUMS +1 -1
- package/dist/car-runtime-1.24.0.tgz +0 -0
- package/dist/car-runtime-1.24.0.tgz.sig.bundle +1 -0
- package/dist/loop/recover.js +4 -0
- package/dist/loop/settlement.d.ts +39 -0
- package/dist/loop/settlement.js +76 -0
- package/dist/loop/stop.d.ts +8 -1
- package/dist/loop/stop.js +25 -7
- package/dist/release-audit.jsonl +4 -4
- package/dist/runtime-core/chatStep.js +25 -10
- package/dist/runtime-core/errors.d.ts +14 -2
- package/dist/runtime-core/errors.js +11 -3
- package/dist/runtime-core/llm.js +43 -12
- package/dist/runtime-core/types.d.ts +10 -1
- package/dist/sbom.json +14 -9
- package/dist/session/log.js +5 -0
- package/package.json +1 -1
- package/src/loop/recover.ts +4 -0
- package/src/loop/settlement.ts +107 -0
- package/src/loop/stop.ts +21 -7
- package/src/runtime-core/chatStep.ts +33 -10
- package/src/runtime-core/errors.ts +8 -1
- package/src/runtime-core/llm.ts +45 -12
- package/src/runtime-core/types.ts +20 -2
- package/src/session/log.ts +5 -1
- package/dist/car-runtime-1.22.1.tgz +0 -0
- package/dist/car-runtime-1.22.1.tgz.sig.bundle +0 -1
package/dist/SHA256SUMS
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
aa80b65782ac20a0d01289545992a9f89c745fd342eeb5c2c4f41bd84e0ff920 car-runtime-1.24.0.tgz
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"base64Signature":"MEYCIQDiF46jsQyUu5MC5YYxG9m3lolFgWHYy/ZYkf4k3oG0DgIhAJVfUldLgNqb/5Ie8OhzH11EUeulKuNL1ZHufvANg8/R","cert":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUc2RENDQm02Z0F3SUJBZ0lVRVhTRjZBSjNXOS9vK2x4V2xzbjVoUTFpcThZd0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpZeE1URTFNRGsxT0RNeldoY05Nall4TVRFMU1UQXdPRE16V2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVuZUJGeFprRHRCWDBFVHNsNXlnMEg1Y1BJMGJRYTA1QW5RSkwKWHdTRTlPMUtyWmpIYjc3UnN1TTdCT3lNWEliTHoxbDcyR2pZdTRsM0I2bm9HNS9YZEtPQ0JZMHdnZ1dKTUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWZCZ05WSFNNRUdEQVdnQlRmCjArblBWaVFSbHZtbzJPa29WYUxHTGhoa1B6QmlCZ05WSFJFQkFmOEVXREJXaGxSb2RIUndjem92TDJkcGRHaDEKWWk1amIyMHZiSEZqTmpJMEwyTmhjaTF5ZFc1MGFXMWxMeTVuYVhSb2RXSXZkMjl5YTJac2IzZHpMM0psYkdWaApjMlV1ZVcxc1FISmxabk12ZEdGbmN5OHhMakkwTGpBd09RWUtLd1lCQkFHRHZ6QUJBUVFyYUhSMGNITTZMeTkwCmIydGxiaTVoWTNScGIyNXpMbWRwZEdoMVluVnpaWEpqYjI1MFpXNTBMbU52YlRBVkJnb3JCZ0VFQVlPL01BRUMKQkFkeVpXeGxZWE5sTURZR0Npc0dBUVFCZzc4d0FRTUVLR1ZqT1RKak1XRm1ZbVpsTldKbE16ZGlZVEEyWW1ReQpOVGhrWWpGbE1qRTNZVFptWmpaaU4yTXdGUVlLS3dZQkJBR0R2ekFCQkFRSGNtVnNaV0Z6WlRBZ0Jnb3JCZ0VFCkFZTy9NQUVGQkJKc2NXTTJNalF2WTJGeUxYSjFiblJwYldVd0hnWUtLd1lCQkFHRHZ6QUJCZ1FRY21WbWN5OTAKWVdkekx6RXVNalF1TURBN0Jnb3JCZ0VFQVlPL01BRUlCQzBNSzJoMGRIQnpPaTh2ZEc5clpXNHVZV04wYVc5dQpjeTVuYVhSb2RXSjFjMlZ5WTI5dWRHVnVkQzVqYjIwd1pBWUtLd1lCQkFHRHZ6QUJDUVJXREZSb2RIUndjem92CkwyZHBkR2gxWWk1amIyMHZiSEZqTmpJMEwyTmhjaTF5ZFc1MGFXMWxMeTVuYVhSb2RXSXZkMjl5YTJac2IzZHoKTDNKbGJHVmhjMlV1ZVcxc1FISmxabk12ZEdGbmN5OHhMakkwTGpBd09BWUtLd1lCQkFHRHZ6QUJDZ1FxRENobApZemt5WXpGaFptSm1aVFZpWlRNM1ltRXdObUprTWpVNFpHSXhaVEl4TjJFMlptWTJZamRqTUIwR0Npc0dBUVFCCmc3OHdBUXNFRHd3TloybDBhSFZpTFdodmMzUmxaREExQmdvckJnRUVBWU8vTUFFTUJDY01KV2gwZEhCek9pOHYKWjJsMGFIVmlMbU52YlM5c2NXTTJNalF2WTJGeUxYSjFiblJwYldVd09BWUtLd1lCQkFHRHZ6QUJEUVFxRENobApZemt5WXpGaFptSm1aVFZpWlRNM1ltRXdObUprTWpVNFpHSXhaVEl4TjJFMlptWTJZamRqTUNBR0Npc0dBUVFCCmc3OHdBUTRFRWd3UWNtVm1jeTkwWVdkekx6RXVNalF1TURBYUJnb3JCZ0VFQVlPL01BRVBCQXdNQ2pFek5qRXgKT1RJM01UUXdLUVlLS3dZQkJBR0R2ekFCRUFRYkRCbG9kSFJ3Y3pvdkwyZHBkR2gxWWk1amIyMHZiSEZqTmpJMApNQmdHQ2lzR0FRUUJnNzh3QVJFRUNnd0lOamt5TXpRMU5qUXdaQVlLS3dZQkJBR0R2ekFCRWdSV0RGUm9kSFJ3CmN6b3ZMMmRwZEdoMVlpNWpiMjB2YkhGak5qSTBMMk5oY2kxeWRXNTBhVzFsTHk1bmFYUm9kV0l2ZDI5eWEyWnMKYjNkekwzSmxiR1ZoYzJVdWVXMXNRSEpsWm5NdmRHRm5jeTh4TGpJMExqQXdPQVlLS3dZQkJBR0R2ekFCRXdRcQpEQ2hsWXpreVl6RmhabUptWlRWaVpUTTNZbUV3Tm1Ka01qVTRaR0l4WlRJeE4yRTJabVkyWWpkak1CY0dDaXNHCkFRUUJnNzh3QVJRRUNRd0hjbVZzWldGelpUQlpCZ29yQmdFRUFZTy9NQUVWQkVzTVNXaDBkSEJ6T2k4dloybDAKYUhWaUxtTnZiUzlzY1dNMk1qUXZZMkZ5TFhKMWJuUnBiV1V2WVdOMGFXOXVjeTl5ZFc1ekx6UXpOekF4TnpnNApNRE0yTDJGMGRHVnRjSFJ6THpFd0ZnWUtLd1lCQkFHRHZ6QUJGZ1FJREFad2RXSnNhV013VUFZS0t3WUJCQUdECnZ6QUJHQVJDREVCeVpYQnZPbXh4WXpZeU5FQTJPVEl6TkRVMk5DOWpZWEl0Y25WdWRHbHRaVUF4TXpZeE1Ua3kKTnpFME9uSmxaanB5WldaekwzUmhaM012TVM0eU5DNHdNSUdMQmdvckJnRUVBZFo1QWdRQ0JIMEVld0I1QUhjQQozVDB3YXNiSEVUSmpHUjRjbVdjM0FxSktYcmplUEszL2g0cHlnQzhwN280QUFBR2gzcVR1bEFBQUJBTUFTREJHCkFpRUE2TnI0Z2VIQUh4cVN0K1cwenJKVXdCMk1ReFQ2Yjh4NVI1OVBoc1IvN2FvQ0lRQ1FvT1JWTFgrc0RmMzkKbVRiZklJYkpaQktEMGtPYnZJY0dMTllHWEhFdDl6QUtCZ2dxaGtqT1BRUURBd05vQURCbEFqQUpWSGZsQ2JNZApMWWtDWndDTmMzVTRGcUVwVzFsQ3JmcTdyMDNJUW9aajM3eUJUTDhWVkZYSFlEQlczUmtGNTBZQ01RQ25COTROCk9LVkhXRkJGM3E5TFlSRjhXN1ZnTkdJbE1rOUdXb2tFbm50R3FIWVBGVGF2WVJhWGxQWmNCN2dYbytrPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==","rekorBundle":{"SignedEntryTimestamp":"MEUCIQDl3bXOX15YpnZ1dQThXg/rqc4GueDfrRcJ9xfgTb4HTgIgfiTDW3QOsmV4fWzLGKtxSDvM64AG/1HJkNoDDnxIJ58=","Payload":{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJhYTgwYjY1NzgyYWMyMGEwZDAxMjg5NTQ1OTkyYTlmODljNzQ1ZmQzNDJlZWI1YzJjNGY0MWJkODRlMGZmOTIwIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FWUNJUURpRjQ2anNReVV1NU1DNVlZeEc5bTNsb2xGZ1dIWXkvWllrZjRrM29HMERnSWhBSlZmVWxkTGdOcWIvNUllOE9oekgxMUVVZXVsS3VOTDFaSHVmdkFOZzgvUiIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVYzJSRU5EUW0wMlowRjNTVUpCWjBsVlJWaFRSalpCU2pOWE9TOXZLMng0VjJ4emJqVm9VVEZwY1RoWmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFplRTFVUlRGTlJHc3hUMFJOZWxkb1kwNU5hbGw0VFZSRk1VMVVRWGRQUkUxNlYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZ1WlVKR2VGcHJSSFJDV0RCRlZITnNOWGxuTUVnMVkxQkpNR0pSWVRBMVFXNVJTa3dLV0hkVFJUbFBNVXR5V21wSVlqYzNVbk4xVFRkQ1QzbE5XRWxpVEhveGJEY3lSMnBaZFRSc00wSTJibTlITlM5WVpFdFBRMEpaTUhkbloxZEtUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXWkNaMDVXU0ZOTlJVZEVRVmRuUWxSbUNqQXJibEJXYVZGU2JIWnRiekpQYTI5V1lVeEhUR2hvYTFCNlFtbENaMDVXU0ZKRlFrRm1PRVZYUkVKWGFHeFNiMlJJVW5kamVtOTJUREprY0dSSGFERUtXV2sxYW1JeU1IWmlTRVpxVG1wSk1Fd3lUbWhqYVRGNVpGYzFNR0ZYTVd4TWVUVnVZVmhTYjJSWFNYWmtNamw1WVRKYWMySXpaSHBNTTBwc1lrZFdhQXBqTWxWMVpWY3hjMUZJU214YWJrMTJaRWRHYm1ONU9IaE1ha2t3VEdwQmQwOVJXVXRMZDFsQ1FrRkhSSFo2UVVKQlVWRnlZVWhTTUdOSVRUWk1lVGt3Q21JeWRHeGlhVFZvV1ROU2NHSXlOWHBNYldSd1pFZG9NVmx1Vm5wYVdFcHFZakkxTUZwWE5UQk1iVTUyWWxSQlZrSm5iM0pDWjBWRlFWbFBMMDFCUlVNS1FrRmtlVnBYZUd4WldFNXNUVVJaUjBOcGMwZEJVVkZDWnpjNGQwRlJUVVZMUjFacVQxUkthazFYUm0xWmJWcHNUbGRLYkUxNlpHbFpWRUV5V1cxUmVRcE9WR2hyV1dwR2JFMXFSVE5aVkZwdFdtcGFhVTR5VFhkR1VWbExTM2RaUWtKQlIwUjJla0ZDUWtGUlNHTnRWbk5hVjBaNldsUkJaMEpuYjNKQ1owVkZDa0ZaVHk5TlFVVkdRa0pLYzJOWFRUSk5hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFZkMGhuV1V0TGQxbENRa0ZIUkhaNlFVSkNaMUZSWTIxV2JXTjVPVEFLV1Zka2VreDZSWFZOYWxGMVRVUkJOMEpuYjNKQ1owVkZRVmxQTDAxQlJVbENRekJOU3pKb01HUklRbnBQYVRoMlpFYzVjbHBYTkhWWlYwNHdZVmM1ZFFwamVUVnVZVmhTYjJSWFNqRmpNbFo1V1RJNWRXUkhWblZrUXpWcVlqSXdkMXBCV1V0TGQxbENRa0ZIUkhaNlFVSkRVVkpYUkVaU2IyUklVbmRqZW05MkNrd3laSEJrUjJneFdXazFhbUl5TUhaaVNFWnFUbXBKTUV3eVRtaGphVEY1WkZjMU1HRlhNV3hNZVRWdVlWaFNiMlJYU1haa01qbDVZVEphYzJJelpIb0tURE5LYkdKSFZtaGpNbFYxWlZjeGMxRklTbXhhYmsxMlpFZEdibU41T0hoTWFra3dUR3BCZDA5QldVdExkMWxDUWtGSFJIWjZRVUpEWjFGeFJFTm9iQXBaZW10NVdYcEdhRnB0U20xYVZGWnBXbFJOTTFsdFJYZE9iVXByVFdwVk5GcEhTWGhhVkVsNFRqSkZNbHB0V1RKWmFtUnFUVUl3UjBOcGMwZEJVVkZDQ21jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacFRGZG9kbU16VW14YVJFRXhRbWR2Y2tKblJVVkJXVTh2VFVGRlRVSkRZMDFLVjJnd1pFaENlazlwT0hZS1dqSnNNR0ZJVm1sTWJVNTJZbE01YzJOWFRUSk5hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFZkMDlCV1V0TGQxbENRa0ZIUkhaNlFVSkVVVkZ4UkVOb2JBcFplbXQ1V1hwR2FGcHRTbTFhVkZacFdsUk5NMWx0UlhkT2JVcHJUV3BWTkZwSFNYaGFWRWw0VGpKRk1scHRXVEpaYW1ScVRVTkJSME5wYzBkQlVWRkNDbWMzT0hkQlVUUkZSV2QzVVdOdFZtMWplVGt3V1Zka2VreDZSWFZOYWxGMVRVUkJZVUpuYjNKQ1owVkZRVmxQTDAxQlJWQkNRWGROUTJwRmVrNXFSWGdLVDFSSk0wMVVVWGRMVVZsTFMzZFpRa0pCUjBSMmVrRkNSVUZSWWtSQ2JHOWtTRkozWTNwdmRrd3laSEJrUjJneFdXazFhbUl5TUhaaVNFWnFUbXBKTUFwTlFtZEhRMmx6UjBGUlVVSm5OemgzUVZKRlJVTm5kMGxPYW10NVRYcFJNVTVxVVhkYVFWbExTM2RaUWtKQlIwUjJla0ZDUldkU1YwUkdVbTlrU0ZKM0NtTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJZa2hHYWs1cVNUQk1NazVvWTJreGVXUlhOVEJoVnpGc1RIazFibUZZVW05a1YwbDJaREk1ZVdFeVduTUtZak5rZWt3elNteGlSMVpvWXpKVmRXVlhNWE5SU0Vwc1dtNU5kbVJIUm01amVUaDRUR3BKTUV4cVFYZFBRVmxMUzNkWlFrSkJSMFIyZWtGQ1JYZFJjUXBFUTJoc1dYcHJlVmw2Um1oYWJVcHRXbFJXYVZwVVRUTlpiVVYzVG0xS2EwMXFWVFJhUjBsNFdsUkplRTR5UlRKYWJWa3lXV3BrYWsxQ1kwZERhWE5IQ2tGUlVVSm5OemgzUVZKUlJVTlJkMGhqYlZaeldsZEdlbHBVUWxwQ1oyOXlRbWRGUlVGWlR5OU5RVVZXUWtWelRWTlhhREJrU0VKNlQyazRkbG95YkRBS1lVaFdhVXh0VG5aaVV6bHpZMWROTWsxcVVYWlpNa1o1VEZoS01XSnVVbkJpVjFWMldWZE9NR0ZYT1hWamVUbDVaRmMxZWt4NlVYcE9la0Y0VG5wbk5BcE5SRTB5VERKR01HUkhWblJqU0ZKNlRIcEZkMFpuV1V0TGQxbENRa0ZIUkhaNlFVSkdaMUZKUkVGYWQyUlhTbk5oVjAxM1ZVRlpTMHQzV1VKQ1FVZEVDblo2UVVKSFFWSkRSRVZDZVZwWVFuWlBiWGg0V1hwWmVVNUZRVEpQVkVsNlRrUlZNazVET1dwWldFbDBZMjVXZFdSSGJIUmFWVUY0VFhwWmVFMVVhM2tLVG5wRk1FOXVTbXhhYW5CNVdsZGFla3d6VW1oYU0wMTJUVk0wZVU1RE5IZE5TVWRNUW1kdmNrSm5SVVZCWkZvMVFXZFJRMEpJTUVWbGQwSTFRVWhqUVFvelZEQjNZWE5pU0VWVVNtcEhValJqYlZkak0wRnhTa3RZY21wbFVFc3pMMmcwY0hsblF6aHdOMjgwUVVGQlIyZ3pjVlIxYkVGQlFVSkJUVUZUUkVKSENrRnBSVUUyVG5JMFoyVklRVWg0Y1ZOMEsxY3dlbkpLVlhkQ01rMVJlRlEyWWpoNE5WSTFPVkJvYzFJdk4yRnZRMGxSUTFGdlQxSldURmdyYzBSbU16a0tiVlJpWmtsSllrcGFRa3RFTUd0UFluWkpZMGRNVGxsSFdFaEZkRGw2UVV0Q1oyZHhhR3RxVDFCUlVVUkJkMDV2UVVSQ2JFRnFRVXBXU0dac1EySk5aQXBNV1d0RFduZERUbU16VlRSR2NVVndWekZzUTNKbWNUZHlNRE5KVVc5YWFqTTNlVUpVVERoV1ZrWllTRmxFUWxjelVtdEdOVEJaUTAxUlEyNUNPVFJPQ2s5TFZraFhSa0pHTTNFNVRGbFNSamhYTjFablRrZEpiRTFyT1VkWGIydEZibTUwUjNGSVdWQkdWR0YyV1ZKaFdHeFFXbU5DTjJkWWJ5dHJQUW90TFMwdExVVk9SQ0JEUlZKVVNVWkpRMEZVUlMwdExTMHRDZz09In19fX0=","integratedTime":1794736714,"logIndex":3899685861,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}}
|
package/dist/loop/recover.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { appendSettlement } from './settlement.js';
|
|
1
2
|
/** 扫描日志中未闭合的 turn(有 turnStart 语义的用户事件但没有 turnEnd)与未配对 toolCall */
|
|
2
3
|
export function recoverInterrupted(log) {
|
|
3
4
|
const report = { turnsClosed: 0, pairedCalls: 0 };
|
|
@@ -31,7 +32,10 @@ export function recoverInterrupted(log) {
|
|
|
31
32
|
report.pairedCalls++;
|
|
32
33
|
}
|
|
33
34
|
// 未闭合 turn → 补记 turnEnd=interrupted(不可与 aborted 混用)
|
|
35
|
+
// 1.24-W2(D-W2-2):补记前先落 settlement——部分链对账 = hasUnknownOutcomes 主战场
|
|
36
|
+
// (上方成对补记的 recovered:true 合成结果在结算里按「结果不明」显式记账,不丢信号)
|
|
34
37
|
for (const turnId of openTurns) {
|
|
38
|
+
appendSettlement(log, turnId, { reason: 'interrupted', recovered: true });
|
|
35
39
|
log.append('runtime', 'turnEnd', turnId, null, { reason: 'interrupted', recovered: true });
|
|
36
40
|
report.turnsClosed++;
|
|
37
41
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 1.24-W2 · 回合结算回执(settlement receipt)
|
|
3
|
+
*
|
|
4
|
+
* 口径(《Qwen-Code 对照分析》§2.3/§5 / 借鉴四篇裁决记录 Q-W2 / 迭代规划 D-W2-1~6):
|
|
5
|
+
* - 「结算必留回执」:任何 turn 无论成败都有一张结构化凭据——观测 / 恢复 / 审计消费同一张
|
|
6
|
+
* - 落链形态 = kind:'system' + payload.type:'settlement'(EventKind 零新增;payload.type 扩展
|
|
7
|
+
* 沿 routing 'decide' / memory 'write|edit|delete|rollback' 先例);一 turn 一张,llm 面与
|
|
8
|
+
* tool 节合并(D-W2-1)
|
|
9
|
+
* - 产出点仅两处:runTurn closeTurn(turnEnd 之前——turnEnd 保持终止标记语义)与
|
|
10
|
+
* recoverInterrupted 补记(部分链对账);authz 台账 turnEnd 与 facade sessionStop 不经
|
|
11
|
+
* closeTurn、不触发(D-W2-2 登记)
|
|
12
|
+
* - 事实源 = 链(非运行时状态):两本账按链上 toolCall/toolResult payload.id 对账;
|
|
13
|
+
* permissionDenialCount = toolResult error:'authorization-denied' 且 granted===false;
|
|
14
|
+
* hasUnknownOutcomes = 无配对 toolResult 或配对结果 recovered:true(副作用状态未知显式记账);
|
|
15
|
+
* bounded = aborted-before-dispatch 补记形态在链(取消落在派发边界,未打断在途执行)
|
|
16
|
+
* - 字段 CAR 化:outcome 由 reason 映射(complete|interrupted|failed);failureKind = 1.23
|
|
17
|
+
* meta.kind 诚实缺键;assistantSeq 代 Qwen assistantMessageUuid;Qwen nextMaxOutputTokens
|
|
18
|
+
* 在 CAR 无对应机制 → 诚实缺键不预埋(D-W2-3 偏差登记)
|
|
19
|
+
* - turnEnd meta 零变化:settlement 是独立事件非 meta 尾键(D-W2-4,s78-16 字节兼容锚不动);
|
|
20
|
+
* stopRequested 经 closeTurn hints 传递不落 meta
|
|
21
|
+
* - kill-switch:CAR_SETTLEMENT=off 跳过 append(K1 env 逐 turn 求值先例);缺省开(D-W2-5)
|
|
22
|
+
* - 链量纪律(R-W2):定长小 payload——账本数组 cap 64 + truncated 标记;内容类信息不进链
|
|
23
|
+
*/
|
|
24
|
+
import type { SessionLog } from '../session/log.ts';
|
|
25
|
+
export type SettlementOutcome = 'complete' | 'interrupted' | 'failed';
|
|
26
|
+
/** 账本数组上限(超出截断 + ledgerTruncated 标记——防病态长 turn 链量失控) */
|
|
27
|
+
export declare const SETTLEMENT_LEDGER_CAP = 64;
|
|
28
|
+
export interface SettlementHints {
|
|
29
|
+
/** terminate 型收口(工具主动 stop 语义,stop.ts closeTurn hints——不落 turnEnd meta) */
|
|
30
|
+
stopRequested?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** TurnEndReason → settlement outcome 映射(completed/max-tokens=complete;aborted/interrupted=interrupted;error/blocked=failed) */
|
|
33
|
+
export declare function outcomeFromReason(reason: string): SettlementOutcome;
|
|
34
|
+
/**
|
|
35
|
+
* 结算回执落链(closeTurn / recover 共用单点)。meta = 同 turn turnEnd 将携带的 meta
|
|
36
|
+
* (reason/kind 等事实同源);usage/hints 缺省时从链自证(requestSent 由 usage 事件或
|
|
37
|
+
* assistant 在场推导)。
|
|
38
|
+
*/
|
|
39
|
+
export declare function appendSettlement(log: SessionLog, turnId: string, meta: Record<string, unknown>, hints?: SettlementHints): void;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** 账本数组上限(超出截断 + ledgerTruncated 标记——防病态长 turn 链量失控) */
|
|
2
|
+
export const SETTLEMENT_LEDGER_CAP = 64;
|
|
3
|
+
/** TurnEndReason → settlement outcome 映射(completed/max-tokens=complete;aborted/interrupted=interrupted;error/blocked=failed) */
|
|
4
|
+
export function outcomeFromReason(reason) {
|
|
5
|
+
if (reason === 'completed' || reason === 'max-tokens')
|
|
6
|
+
return 'complete';
|
|
7
|
+
if (reason === 'aborted' || reason === 'interrupted')
|
|
8
|
+
return 'interrupted';
|
|
9
|
+
return 'failed';
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* 结算回执落链(closeTurn / recover 共用单点)。meta = 同 turn turnEnd 将携带的 meta
|
|
13
|
+
* (reason/kind 等事实同源);usage/hints 缺省时从链自证(requestSent 由 usage 事件或
|
|
14
|
+
* assistant 在场推导)。
|
|
15
|
+
*/
|
|
16
|
+
export function appendSettlement(log, turnId, meta, hints) {
|
|
17
|
+
if (process.env.CAR_SETTLEMENT === 'off')
|
|
18
|
+
return;
|
|
19
|
+
const reason = typeof meta.reason === 'string' ? meta.reason : 'error';
|
|
20
|
+
const kind = typeof meta.kind === 'string' ? meta.kind : undefined;
|
|
21
|
+
// 单次链扫描:两本账 + 拒绝计数 + unknown/bounded 事实 + requestSent 事实 + assistantSeq
|
|
22
|
+
const declared = [];
|
|
23
|
+
const resultById = new Map();
|
|
24
|
+
let denialCount = 0;
|
|
25
|
+
let bounded = false;
|
|
26
|
+
let usageRequests = 0;
|
|
27
|
+
let assistantSeq;
|
|
28
|
+
for (const e of log.events) {
|
|
29
|
+
if (e.turnId !== turnId)
|
|
30
|
+
continue;
|
|
31
|
+
const p = (e.payload ?? {});
|
|
32
|
+
if (e.kind === 'toolCall') {
|
|
33
|
+
if (typeof p.id === 'string')
|
|
34
|
+
declared.push(p.id);
|
|
35
|
+
}
|
|
36
|
+
else if (e.kind === 'toolResult') {
|
|
37
|
+
if (typeof p.id === 'string') {
|
|
38
|
+
resultById.set(p.id, p); // 1.6-S1 last-wins 同款口径
|
|
39
|
+
if (p.error === 'authorization-denied' && p.granted === false)
|
|
40
|
+
denialCount++;
|
|
41
|
+
if (p.error === 'aborted-before-dispatch')
|
|
42
|
+
bounded = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else if (e.kind === 'usage') {
|
|
46
|
+
usageRequests++;
|
|
47
|
+
}
|
|
48
|
+
else if (e.kind === 'assistant') {
|
|
49
|
+
assistantSeq = e.seq;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const dispatched = [...resultById.keys()];
|
|
53
|
+
const unknownIds = declared.filter((id) => {
|
|
54
|
+
const res = resultById.get(id);
|
|
55
|
+
return !res || res.recovered === true;
|
|
56
|
+
});
|
|
57
|
+
const cap = (arr) => (arr.length > SETTLEMENT_LEDGER_CAP ? arr.slice(0, SETTLEMENT_LEDGER_CAP) : arr);
|
|
58
|
+
const ledgerTruncated = declared.length > SETTLEMENT_LEDGER_CAP || dispatched.length > SETTLEMENT_LEDGER_CAP;
|
|
59
|
+
log.append('runtime', 'system', turnId, {
|
|
60
|
+
type: 'settlement',
|
|
61
|
+
outcome: outcomeFromReason(reason),
|
|
62
|
+
...(kind ? { failureKind: kind } : {}),
|
|
63
|
+
requestSent: usageRequests > 0 || assistantSeq !== undefined,
|
|
64
|
+
...(assistantSeq !== undefined ? { assistantSeq } : {}),
|
|
65
|
+
declaredToolUseIds: cap(declared),
|
|
66
|
+
dispatchedToolUseIds: cap(dispatched),
|
|
67
|
+
...(ledgerTruncated ? { ledgerTruncated: true } : {}),
|
|
68
|
+
tool: {
|
|
69
|
+
permissionDenialCount: denialCount,
|
|
70
|
+
hasUnknownOutcomes: unknownIds.length > 0,
|
|
71
|
+
...(unknownIds.length ? { unknownToolUseIds: cap(unknownIds) } : {}),
|
|
72
|
+
stopRequested: hints?.stopRequested === true,
|
|
73
|
+
bounded,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
package/dist/loop/stop.d.ts
CHANGED
|
@@ -84,8 +84,15 @@ export interface TurnResult {
|
|
|
84
84
|
reason: TurnEndReason;
|
|
85
85
|
}>;
|
|
86
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* 1.23-W1(D-W1-6):取消时机标注(W-0 审计 F-W0-3——aborted 三路径链上不区分)——
|
|
89
|
+
* 'before-request' = 请求未发出(runTurn 循环头 / chatStep 零 chunk 流结束检查);
|
|
90
|
+
* 'in-flight' = 在途打断(chatStep chunk 间隙 / 有 chunk 后的流结束检查)。
|
|
91
|
+
* 缺省缺席 = 1.22 形态(terminate 型 abort 为工具主动收口语义,非取消,不带键)。
|
|
92
|
+
*/
|
|
87
93
|
export declare class TurnAborted extends Error {
|
|
88
|
-
|
|
94
|
+
readonly abortedAt?: 'before-request' | 'in-flight' | undefined;
|
|
95
|
+
constructor(abortedAt?: 'before-request' | 'in-flight' | undefined);
|
|
89
96
|
}
|
|
90
97
|
export declare function runTurn(opts: {
|
|
91
98
|
log: SessionLog;
|
package/dist/loop/stop.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { CarM8Error } from '../runtime-core/errors.js';
|
|
2
|
+
import { appendSettlement } from './settlement.js';
|
|
3
|
+
/**
|
|
4
|
+
* 1.23-W1(D-W1-6):取消时机标注(W-0 审计 F-W0-3——aborted 三路径链上不区分)——
|
|
5
|
+
* 'before-request' = 请求未发出(runTurn 循环头 / chatStep 零 chunk 流结束检查);
|
|
6
|
+
* 'in-flight' = 在途打断(chatStep chunk 间隙 / 有 chunk 后的流结束检查)。
|
|
7
|
+
* 缺省缺席 = 1.22 形态(terminate 型 abort 为工具主动收口语义,非取消,不带键)。
|
|
8
|
+
*/
|
|
2
9
|
export class TurnAborted extends Error {
|
|
3
|
-
|
|
10
|
+
abortedAt;
|
|
11
|
+
constructor(abortedAt) {
|
|
12
|
+
super('CAR-ABORTED: turn cancelled by user');
|
|
13
|
+
this.abortedAt = abortedAt;
|
|
14
|
+
}
|
|
4
15
|
}
|
|
5
16
|
/**
|
|
6
17
|
* 1.11-S2(D-33 / 计量设计 §3.3):turn 级 usage 聚合——扫描本 turn 的 'usage' 事件(事实本体
|
|
@@ -45,13 +56,16 @@ export async function runTurn(opts) {
|
|
|
45
56
|
let steps = 0;
|
|
46
57
|
// 1.11-S2(D-33):turnEnd 落链单点包装——聚合本 turn usage 事件(无 usage 不带键,
|
|
47
58
|
// 缺省=未采集向后兼容);reason 保持 meta 首键,usage 恒为加法尾键
|
|
48
|
-
|
|
59
|
+
// 1.24-W2(D-W2-2):turnEnd 之前先落 settlement 结算回执(turnEnd 保持终止标记语义;
|
|
60
|
+
// hints 仅 terminate 收口传递 stopRequested——不落 turnEnd meta,D-W2-4)
|
|
61
|
+
const closeTurn = (meta, hints) => {
|
|
49
62
|
const usage = turnUsageFrom(log, turnId);
|
|
50
63
|
// 1.18.2-K8(R-K7-2):计量缺失 fail-visible——商汤网关实测 20% 概率 message_delta
|
|
51
64
|
// 不带 usage(流正常回复但计量整条缺席)→ 缺失口径纪律不落半条事实(turnUsageFrom
|
|
52
65
|
// undefined),缺失本身在此显式标注:本 turn 存在 assistant 事件(模型有可见输出 ⇒
|
|
53
66
|
// 必有请求)而无 usage 聚合时带 usageMissing: true(加法尾键,与 usage 同语义位)
|
|
54
67
|
const hasAssistant = log.events.some((e) => e.kind === 'assistant' && e.turnId === turnId);
|
|
68
|
+
appendSettlement(log, turnId, meta, hints);
|
|
55
69
|
return log.append('runtime', 'turnEnd', turnId, null, {
|
|
56
70
|
...meta,
|
|
57
71
|
...(usage ? { usage } : hasAssistant ? { usageMissing: true } : {}),
|
|
@@ -166,7 +180,8 @@ export async function runTurn(opts) {
|
|
|
166
180
|
try {
|
|
167
181
|
while (steps < maxSteps) {
|
|
168
182
|
if (signal?.aborted) {
|
|
169
|
-
|
|
183
|
+
// 1.23-W1(D-W1-6):循环头取消 = 请求未发出(无在途请求)
|
|
184
|
+
const seq = closeTurn({ reason: 'aborted', abortedAt: 'before-request' }).seq;
|
|
170
185
|
trail.push({ seq, reason: 'aborted' });
|
|
171
186
|
return { reason: 'aborted', steps, endReasonTrail: trail };
|
|
172
187
|
}
|
|
@@ -226,7 +241,8 @@ export async function runTurn(opts) {
|
|
|
226
241
|
const agg = preset.aggregate ?? {};
|
|
227
242
|
const abortedNow = (agg.terminate ?? 'all') === 'any' ? batch.terminateVotes > 0 : batch.terminateAll;
|
|
228
243
|
if (abortedNow) { // terminate 收口:默认 AND(整批),可配置 any
|
|
229
|
-
|
|
244
|
+
// 1.24-W2:工具主动 stop 语义 → settlement stopRequested=true(hints 不落 meta,D-W2-4)
|
|
245
|
+
const seq = closeTurn({ reason: 'aborted' }, { stopRequested: true }).seq;
|
|
230
246
|
trail.push({ seq, reason: 'aborted' });
|
|
231
247
|
return { reason: 'aborted', steps, endReasonTrail: trail };
|
|
232
248
|
}
|
|
@@ -252,17 +268,19 @@ export async function runTurn(opts) {
|
|
|
252
268
|
}
|
|
253
269
|
catch (e) {
|
|
254
270
|
if (e instanceof TurnAborted) {
|
|
255
|
-
|
|
271
|
+
// 1.23-W1(D-W1-6):chatStep 甄别过的取消时机随异常携带(terminate 型 abort 不经此路径)
|
|
272
|
+
const seq = closeTurn({ reason: 'aborted', ...(e.abortedAt ? { abortedAt: e.abortedAt } : {}) }).seq;
|
|
256
273
|
trail.push({ seq, reason: 'aborted' });
|
|
257
274
|
return { reason: 'aborted', steps, endReasonTrail: trail };
|
|
258
275
|
}
|
|
259
276
|
// 1.4-S4:CarM8Error 保形——code/userHint/retryable 进 turnEnd(A080001 引导文案「请运行
|
|
260
|
-
// car doctor」可达用户;非 CarM8Error 维持 String(e)
|
|
277
|
+
// car doctor」可达用户;非 CarM8Error 维持 String(e) 口径不变);
|
|
278
|
+
// 1.23-W1(D-W1-3):+kind 失败细类(加法尾键——B080001 多形态分型,审计 F-W0-2)
|
|
261
279
|
const m8 = e instanceof CarM8Error;
|
|
262
280
|
const detail = m8 ? `${e.slug}: ${e.message}` : String(e);
|
|
263
281
|
const seq = closeTurn({
|
|
264
282
|
reason: 'error', detail,
|
|
265
|
-
...(m8 ? { code: e.code, userHint: e.userHint, retryable: e.retryable } : {}),
|
|
283
|
+
...(m8 ? { code: e.code, userHint: e.userHint, retryable: e.retryable, ...(e.kind ? { kind: e.kind } : {}) } : {}),
|
|
266
284
|
}).seq;
|
|
267
285
|
trail.push({ seq, reason: 'error' });
|
|
268
286
|
return { reason: 'error', steps, endReasonTrail: trail };
|
package/dist/release-audit.jsonl
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{"stage":"archive","detail":"git archive HEAD → car-runtime-1.
|
|
2
|
-
{"stage":"sha256sums","detail":"SHA256SUMS
|
|
3
|
-
{"stage":"sbom","detail":"
|
|
4
|
-
{"stage":"cosign-keyless","detail":"signed car-runtime-1.
|
|
1
|
+
{"stage":"archive","detail":"git archive HEAD → car-runtime-1.24.0.tgz(455869 bytes,树哈希锚定)","ts":1794736712665}
|
|
2
|
+
{"stage":"sha256sums","detail":"SHA256SUMS aa80b65782ac20a0…(裸字节复核通过)","ts":1794736712666}
|
|
3
|
+
{"stage":"sbom","detail":"56 个组件(运行时依赖 0 项)","ts":1794736712672}
|
|
4
|
+
{"stage":"cosign-keyless","detail":"signed car-runtime-1.24.0.tgz (keyless OIDC + Rekor)","ts":1794736714665}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TurnAborted } from '../loop/stop.js';
|
|
2
2
|
import { StreamRedactor } from './redaction.js';
|
|
3
3
|
import { scanSecrets } from '../security/secrets.js';
|
|
4
|
-
import { providerUnreachable } from './errors.js';
|
|
4
|
+
import { CarM8Error, providerUnreachable } from './errors.js';
|
|
5
5
|
export async function chatStep(opts) {
|
|
6
6
|
const { core, log, turnId, model, tools, maxTokens, adapterId } = opts;
|
|
7
7
|
// 1. 请求前断言(SQ-07 #2):N1 失守 = 带病请求,显式拒绝
|
|
@@ -49,6 +49,12 @@ export async function chatStep(opts) {
|
|
|
49
49
|
const thinkingRedactor = new StreamRedactor();
|
|
50
50
|
let stopReason;
|
|
51
51
|
let errorDetail;
|
|
52
|
+
// 1.23-W1(D-W1-2):error chunk 细类捕获(最后一条 error chunk 的 kind——providerUnreachable
|
|
53
|
+
// 携带 → runTurn turnEnd meta.kind;缺失 = 1.22 形态)
|
|
54
|
+
let errorKind;
|
|
55
|
+
// 1.23-W1(D-W1-6):零 chunk 甄别——流结束检查抛 TurnAborted 时区分「请求未发出(attempt 前
|
|
56
|
+
// 静默收口)」与「在途打断」;chunk 间隙路径恒 in-flight
|
|
57
|
+
let sawChunk = false;
|
|
52
58
|
let usage;
|
|
53
59
|
const stream = core.chat({
|
|
54
60
|
model,
|
|
@@ -62,9 +68,11 @@ export async function chatStep(opts) {
|
|
|
62
68
|
...(opts.log.sessionId ? { metadata: { sessionId: opts.log.sessionId, turnId, traceId: `${opts.log.sessionId}:${turnId}` } } : {}),
|
|
63
69
|
});
|
|
64
70
|
for await (const chunk of stream) {
|
|
65
|
-
|
|
71
|
+
sawChunk = true;
|
|
72
|
+
// 1.4-S4(D-19):chunk 间隙取消——此刻 assistant 尚未落链(流完才 append),无半截消息;
|
|
73
|
+
// 1.23-W1(D-W1-6):在途打断标注 in-flight
|
|
66
74
|
if (opts.signal?.aborted)
|
|
67
|
-
throw new TurnAborted();
|
|
75
|
+
throw new TurnAborted('in-flight');
|
|
68
76
|
if (chunk.delta !== undefined) {
|
|
69
77
|
const masked = redactor.push(chunk.delta);
|
|
70
78
|
text += masked;
|
|
@@ -109,17 +117,21 @@ export async function chatStep(opts) {
|
|
|
109
117
|
opts.onDelta({ thinkingDelta: masked });
|
|
110
118
|
}
|
|
111
119
|
}
|
|
112
|
-
if (chunk.error)
|
|
120
|
+
if (chunk.error) {
|
|
113
121
|
errorDetail = chunk.error.message;
|
|
122
|
+
errorKind = chunk.error.kind; // 1.23-W1(D-W1-2):细类透传(最后一条 error chunk 为准)
|
|
123
|
+
}
|
|
114
124
|
if (chunk.usage)
|
|
115
125
|
usage = chunk.usage; // D-33:附着于最后一个 chunk——后到覆盖
|
|
116
126
|
if (chunk.finishReason !== undefined)
|
|
117
127
|
stopReason = chunk.finishReason === 'aborted' ? 'error' : chunk.finishReason;
|
|
118
128
|
}
|
|
119
129
|
// 1.4-S4(D-19):finish 检查前先查取消(适配器取消路径静默收口无 finishReason——
|
|
120
|
-
//
|
|
130
|
+
// 必须先于此处的契约兜底,否则取消被误判为适配器违规);
|
|
131
|
+
// 1.23-W1(D-W1-6):abortedAt 甄别——有 chunk = 在途打断(in-flight);
|
|
132
|
+
// 零 chunk = attempt 前静默收口,请求未发出(before-request)
|
|
121
133
|
if (opts.signal?.aborted)
|
|
122
|
-
throw new TurnAborted();
|
|
134
|
+
throw new TurnAborted(sawChunk ? 'in-flight' : 'before-request');
|
|
123
135
|
if (stopReason === undefined) {
|
|
124
136
|
// 适配器层已保证首 finishReason(AL-05);防御性兜底显式化
|
|
125
137
|
throw new Error('CAR-E-LLM-FINISH: 流结束无 finishReason(适配器契约违规)');
|
|
@@ -201,16 +213,19 @@ export async function chatStep(opts) {
|
|
|
201
213
|
args = c.argsBuf ? JSON.parse(c.argsBuf) : {};
|
|
202
214
|
}
|
|
203
215
|
catch {
|
|
204
|
-
// toolUse 声明下 args 非法 JSON = 协议违规显式化(非 length 场景无 truncatedTools
|
|
205
|
-
|
|
216
|
+
// toolUse 声明下 args 非法 JSON = 协议违规显式化(非 length 场景无 truncatedTools 口径);
|
|
217
|
+
// 1.23-W1(D-W1-3):转 CarM8Error——turnEnd meta 获得 code/userHint/retryable + kind
|
|
218
|
+
// 'proto-invalid-args'(detail 形态变更登记:CarM8Error 统一格式带(B080001)码后缀)
|
|
219
|
+
throw new CarM8Error('B080001', 'CAR-E-LLM-PROTO', `工具调用 ${c.name} 参数非法 JSON(finishReason=${stopReason},${errorDetail ?? '无附加错误'})`, undefined, false, 'proto-invalid-args');
|
|
206
220
|
}
|
|
207
221
|
toolCalls.push({ id: c.id ?? `tc-${idx}-${c.name}`, tool: c.name, args });
|
|
208
222
|
}
|
|
209
223
|
return { stopReason: 'toolUse', ...(text ? { text } : {}), ...(toolCalls.length ? { toolCalls } : {}), secretsRedacted };
|
|
210
224
|
}
|
|
211
|
-
// error(含 aborted 映射):以 B080001 抛出 → runTurn catch → turnEnd error 携带 detail(BD-04
|
|
225
|
+
// error(含 aborted 映射):以 B080001 抛出 → runTurn catch → turnEnd error 携带 detail(BD-04 收口);
|
|
226
|
+
// 1.23-W1(D-W1-2):细类 kind 透传(error chunk 捕获的 errorKind;缺失 = 1.22 形态)
|
|
212
227
|
if (stopReason === 'error') {
|
|
213
|
-
throw providerUnreachable(errorDetail ?? 'provider 返回 error 终止(无附加说明)');
|
|
228
|
+
throw providerUnreachable(errorDetail ?? 'provider 返回 error 终止(无附加说明)', errorKind);
|
|
214
229
|
}
|
|
215
230
|
return { stopReason: 'stop', ...(text ? { text } : {}), secretsRedacted };
|
|
216
231
|
}
|
|
@@ -16,6 +16,12 @@ export declare class CarM8Error extends Error {
|
|
|
16
16
|
readonly userHint?: string | undefined;
|
|
17
17
|
/** 重试建议(A080001 不重试 / B080001 退避后重试) */
|
|
18
18
|
readonly retryable: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* 1.23-W1(D-W1-3):失败细类(LlmErrorKind 注册表)——B080001 一码多形态的分型标注,
|
|
21
|
+
* 经 runTurn closeTurn 落 turnEnd meta.kind(加法尾键,消费面免字符串反解)。
|
|
22
|
+
* 缺省缺席 = 1.22 形态(A080001 凭据缺失以独立码区分,无需 kind)。
|
|
23
|
+
*/
|
|
24
|
+
readonly kind?: string | undefined;
|
|
19
25
|
constructor(
|
|
20
26
|
/** 六位错误码(§3.5.1 注册表) */
|
|
21
27
|
code: M8ErrorCode,
|
|
@@ -24,7 +30,13 @@ export declare class CarM8Error extends Error {
|
|
|
24
30
|
/** 用户文案(§3.5.1 注册表原文) */
|
|
25
31
|
userHint?: string | undefined,
|
|
26
32
|
/** 重试建议(A080001 不重试 / B080001 退避后重试) */
|
|
27
|
-
retryable?: boolean
|
|
33
|
+
retryable?: boolean,
|
|
34
|
+
/**
|
|
35
|
+
* 1.23-W1(D-W1-3):失败细类(LlmErrorKind 注册表)——B080001 一码多形态的分型标注,
|
|
36
|
+
* 经 runTurn closeTurn 落 turnEnd meta.kind(加法尾键,消费面免字符串反解)。
|
|
37
|
+
* 缺省缺席 = 1.22 形态(A080001 凭据缺失以独立码区分,无需 kind)。
|
|
38
|
+
*/
|
|
39
|
+
kind?: string | undefined);
|
|
28
40
|
}
|
|
29
41
|
export declare function credentialMissing(provider: string, searched: string[]): CarM8Error;
|
|
30
|
-
export declare function providerUnreachable(detail: string): CarM8Error;
|
|
42
|
+
export declare function providerUnreachable(detail: string, kind?: string): CarM8Error;
|
|
@@ -11,6 +11,7 @@ export class CarM8Error extends Error {
|
|
|
11
11
|
slug;
|
|
12
12
|
userHint;
|
|
13
13
|
retryable;
|
|
14
|
+
kind;
|
|
14
15
|
constructor(
|
|
15
16
|
/** 六位错误码(§3.5.1 注册表) */
|
|
16
17
|
code,
|
|
@@ -19,18 +20,25 @@ export class CarM8Error extends Error {
|
|
|
19
20
|
/** 用户文案(§3.5.1 注册表原文) */
|
|
20
21
|
userHint,
|
|
21
22
|
/** 重试建议(A080001 不重试 / B080001 退避后重试) */
|
|
22
|
-
retryable = false
|
|
23
|
+
retryable = false,
|
|
24
|
+
/**
|
|
25
|
+
* 1.23-W1(D-W1-3):失败细类(LlmErrorKind 注册表)——B080001 一码多形态的分型标注,
|
|
26
|
+
* 经 runTurn closeTurn 落 turnEnd meta.kind(加法尾键,消费面免字符串反解)。
|
|
27
|
+
* 缺省缺席 = 1.22 形态(A080001 凭据缺失以独立码区分,无需 kind)。
|
|
28
|
+
*/
|
|
29
|
+
kind) {
|
|
23
30
|
super(`${slug}: ${message}(${code})`);
|
|
24
31
|
this.code = code;
|
|
25
32
|
this.slug = slug;
|
|
26
33
|
this.userHint = userHint;
|
|
27
34
|
this.retryable = retryable;
|
|
35
|
+
this.kind = kind;
|
|
28
36
|
this.name = 'CarM8Error';
|
|
29
37
|
}
|
|
30
38
|
}
|
|
31
39
|
export function credentialMissing(provider, searched) {
|
|
32
40
|
return new CarM8Error('A080001', 'CAR-E-CRED-MISSING', `未找到模型服务凭据(provider=${provider},已检索:${searched.join('、') || '无可用通道'})`, '未找到模型服务凭据,请运行 car doctor', false);
|
|
33
41
|
}
|
|
34
|
-
export function providerUnreachable(detail) {
|
|
35
|
-
return new CarM8Error('B080001', 'CAR-E-LLM-UNREACHABLE', `LLM Provider 不可达/重试耗尽:${detail}`, '模型服务暂时不可用,本轮已停止', true);
|
|
42
|
+
export function providerUnreachable(detail, kind) {
|
|
43
|
+
return new CarM8Error('B080001', 'CAR-E-LLM-UNREACHABLE', `LLM Provider 不可达/重试耗尽:${detail}`, '模型服务暂时不可用,本轮已停止', true, kind);
|
|
36
44
|
}
|
package/dist/runtime-core/llm.js
CHANGED
|
@@ -228,7 +228,9 @@ async function* streamSsePost(spec, parse) {
|
|
|
228
228
|
if (!res.ok) {
|
|
229
229
|
const text = await res.text().catch(() => '');
|
|
230
230
|
// 4xx 业务错不重试(§3.5.4);错误信息不含凭据(凭据值不进错误路径)
|
|
231
|
-
|
|
231
|
+
// 1.23-W1(D-W1-5):4xx 细类标注——overflow 特征命中 = context-limit-suspect(疑似),否则 transport-4xx
|
|
232
|
+
const kind = contextLimitSuspect(text) ? 'context-limit-suspect' : 'transport-4xx';
|
|
233
|
+
yield { finishReason: 'error', error: { code: 'B080001', message: `provider HTTP ${res.status}${text ? `:${text.slice(0, 200)}` : ''}`, kind } };
|
|
232
234
|
return;
|
|
233
235
|
}
|
|
234
236
|
if (!res.body)
|
|
@@ -252,7 +254,8 @@ async function* streamSsePost(spec, parse) {
|
|
|
252
254
|
// 流自然结束但未给 finishReason = 异常终止(AL-05 口径:缺失显式化,禁吞没);
|
|
253
255
|
// 已携带 finishReason 的流原样收口——兜底不得追加(M8-BUG-1 口径延续)
|
|
254
256
|
if (!sawFinish) {
|
|
255
|
-
|
|
257
|
+
// 1.23-W1:细类 no-finish-reason(审计 F-W0-2 拆型)
|
|
258
|
+
yield { finishReason: 'error', error: { code: 'B080001', message: '流结束未携带 finish_reason(AL-05)', kind: 'no-finish-reason' } };
|
|
256
259
|
}
|
|
257
260
|
return;
|
|
258
261
|
}
|
|
@@ -268,11 +271,11 @@ async function* streamSsePost(spec, parse) {
|
|
|
268
271
|
continue;
|
|
269
272
|
}
|
|
270
273
|
if (beforeFirstByte) {
|
|
271
|
-
// 重试耗尽 → B080001(BD-04:消费方 runTurn 以 error
|
|
272
|
-
throw providerUnreachable(`${e.name}: ${e.message}(尝试 ${attempt + 1}/${spec.retries + 1},${Date.now() - started}ms
|
|
274
|
+
// 重试耗尽 → B080001(BD-04:消费方 runTurn 以 error 收口);1.23-W1 细类 transport-exhausted
|
|
275
|
+
throw providerUnreachable(`${e.name}: ${e.message}(尝试 ${attempt + 1}/${spec.retries + 1},${Date.now() - started}ms)`, 'transport-exhausted');
|
|
273
276
|
}
|
|
274
|
-
// 流中途失败:已消费 chunk 不可重放 → error chunk
|
|
275
|
-
yield { finishReason: 'error', error: { code: 'B080001', message: `流中途失败:${e.message}
|
|
277
|
+
// 流中途失败:已消费 chunk 不可重放 → error chunk 收口;1.23-W1 细类 stream-midbreak
|
|
278
|
+
yield { finishReason: 'error', error: { code: 'B080001', message: `流中途失败:${e.message}`, kind: 'stream-midbreak' } };
|
|
276
279
|
return;
|
|
277
280
|
}
|
|
278
281
|
finally {
|
|
@@ -290,6 +293,22 @@ const FINISH_MAP = {
|
|
|
290
293
|
};
|
|
291
294
|
class Retryable extends Error {
|
|
292
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* 1.23-W1(D-W1-5):context-limit-suspect 确定性关键词表——仅用于 4xx 错误体、只收高置信特征;
|
|
298
|
+
* 标注语义 = **疑似**(消费面文案须带「疑似」,禁估算禁 LLM 判断);未命中 = transport-4xx。
|
|
299
|
+
*/
|
|
300
|
+
const CONTEXT_LIMIT_PATTERNS = [
|
|
301
|
+
/context length/i,
|
|
302
|
+
/maximum context/i,
|
|
303
|
+
/context[_ ]window/i,
|
|
304
|
+
/prompt is too long/i,
|
|
305
|
+
/too many tokens/i,
|
|
306
|
+
/input tokens? exceed/i,
|
|
307
|
+
/exceeds? the (maximum )?(context|token)/i,
|
|
308
|
+
/上下文(长度|超限)/,
|
|
309
|
+
/超出上下文/,
|
|
310
|
+
];
|
|
311
|
+
const contextLimitSuspect = (text) => CONTEXT_LIMIT_PATTERNS.some((p) => p.test(text));
|
|
293
312
|
/**
|
|
294
313
|
* 1.11-S1(D-33 / 计量设计 §1.1):openai usage 归一——prompt_tokens 含 cached,inputTokens
|
|
295
314
|
* 必须做减法(prompt − prompt_tokens_details.cached_tokens);cache_creation 恒 0(无对应字段,
|
|
@@ -382,13 +401,20 @@ async function* parseSseStream(body, model, adapterId) {
|
|
|
382
401
|
yield { usage };
|
|
383
402
|
continue;
|
|
384
403
|
}
|
|
404
|
+
// 1.23-W1(D-W1-4 / D-W1-1):finish 终态附带 error 细类——未映射 = unmapped-finish;
|
|
405
|
+
// content_filter(openai 族唯一映射 'aborted' 的信号)= refusal(与 anthropic stop_reason=refusal 对齐)
|
|
406
|
+
const finishError = finish === 'error'
|
|
407
|
+
? { code: 'B080001', message: `未映射的 finish_reason "${raw}"`, kind: 'unmapped-finish' }
|
|
408
|
+
: finish === 'aborted'
|
|
409
|
+
? { code: 'B080001', message: `provider content_filter (finish_reason="${raw}")`, kind: 'refusal' }
|
|
410
|
+
: undefined;
|
|
385
411
|
const chunks = [];
|
|
386
412
|
if (toolCallDeltas.length === 0) {
|
|
387
413
|
chunks.push({
|
|
388
414
|
...(reasoningContent !== undefined ? { thinkingDelta: reasoningContent } : {}),
|
|
389
415
|
...(text !== undefined ? { delta: text } : {}),
|
|
390
416
|
...(finish !== undefined
|
|
391
|
-
? { finishReason: finish, ...(
|
|
417
|
+
? { finishReason: finish, ...(finishError ? { error: finishError } : {}) }
|
|
392
418
|
: {}),
|
|
393
419
|
});
|
|
394
420
|
}
|
|
@@ -400,7 +426,7 @@ async function* parseSseStream(body, model, adapterId) {
|
|
|
400
426
|
...(i === 0 && text !== undefined ? { delta: text } : {}),
|
|
401
427
|
toolCallDelta: toolCallDeltas[i],
|
|
402
428
|
...(last && finish !== undefined
|
|
403
|
-
? { finishReason: finish, ...(
|
|
429
|
+
? { finishReason: finish, ...(finishError ? { error: finishError } : {}) }
|
|
404
430
|
: {}),
|
|
405
431
|
});
|
|
406
432
|
}
|
|
@@ -800,9 +826,13 @@ async function* parseAnthropicSseStream(body, model, adapterId) {
|
|
|
800
826
|
const usage = mergeAnthropicUsage(startUsage, (evt.usage ?? null), model, adapterId);
|
|
801
827
|
if (raw != null) {
|
|
802
828
|
const finish = ANTHROPIC_STOP_REASON_MAP[String(raw)];
|
|
829
|
+
// 1.23-W1(D-W1-4 / D-W1-1):stop_reason 细类标注——refusal 附加 error chunk(kind 'refusal',
|
|
830
|
+
// 与 openai content_filter 对齐;finishReason 映射不动);未映射 = unmapped-finish
|
|
803
831
|
const chunk = finish
|
|
804
|
-
?
|
|
805
|
-
|
|
832
|
+
? finish === 'aborted'
|
|
833
|
+
? { finishReason: finish, error: { code: 'B080001', message: `provider refusal (stop_reason="${raw}")`, kind: 'refusal' } }
|
|
834
|
+
: { finishReason: finish }
|
|
835
|
+
: { finishReason: 'error', error: { code: 'B080001', message: `未映射的 stop_reason "${raw}"`, kind: 'unmapped-finish' } };
|
|
806
836
|
yield usage ? { ...chunk, usage } : chunk;
|
|
807
837
|
}
|
|
808
838
|
else if (usage) {
|
|
@@ -811,8 +841,9 @@ async function* parseAnthropicSseStream(body, model, adapterId) {
|
|
|
811
841
|
}
|
|
812
842
|
}
|
|
813
843
|
else if (type === 'error') {
|
|
814
|
-
// 流内 error 事件(overloaded_error 等)→ error chunk 收口(后续 message_delta
|
|
815
|
-
|
|
844
|
+
// 流内 error 事件(overloaded_error 等)→ error chunk 收口(后续 message_delta 由守卫拦冲突);
|
|
845
|
+
// 1.23-W1 细类 provider-stream-error
|
|
846
|
+
yield { finishReason: 'error', error: { code: 'B080001', message: `provider 流错误:${evt.error?.message ?? JSON.stringify(evt).slice(0, 200)}`, kind: 'provider-stream-error' } };
|
|
816
847
|
}
|
|
817
848
|
}
|
|
818
849
|
}
|
|
@@ -47,6 +47,13 @@ export interface ToolCallDelta {
|
|
|
47
47
|
/** JSON 增量片段,聚合后整体 JSON.parse(半截 JSON 不解析——ADR-001 同纪律) */
|
|
48
48
|
argumentsDelta?: string;
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* 1.23-W1(D-W1-1 / 《W-0 失败分类学对照审计》§5):LLM 失败细类注册表——把 B080001 下压着的
|
|
52
|
+
* 多形态失败拆到可对应处置动作的粒度(审计 F-W0-2)。**加法演进**:可选键,缺失 = 1.22 形态
|
|
53
|
+
* 逐字节兼容。`transport-network` 从审计 8 类剔除(无独立产出点——网络失败只以 exhausted /
|
|
54
|
+
* midbreak 到达消费面);`unmapped-finish` / `provider-stream-error` 为实施中浮出的两形态增补。
|
|
55
|
+
*/
|
|
56
|
+
export type LlmErrorKind = 'transport-4xx' | 'transport-exhausted' | 'stream-midbreak' | 'no-finish-reason' | 'refusal' | 'proto-invalid-args' | 'context-limit-suspect' | 'unmapped-finish' | 'provider-stream-error';
|
|
50
57
|
/**
|
|
51
58
|
* 1.11-S1(D-33 / 计量设计 §3.1):token 四分量归一化用量——跨 provider 可加总的库内口径
|
|
52
59
|
* (§1.1:openai prompt_tokens 含 cached 必须减法归一;anthropic input_tokens 天然不含 cache)。
|
|
@@ -79,10 +86,12 @@ export interface LlmChunk {
|
|
|
79
86
|
delta?: string;
|
|
80
87
|
toolCallDelta?: ToolCallDelta;
|
|
81
88
|
finishReason?: 'stop' | 'length' | 'toolUse' | 'error' | 'aborted';
|
|
82
|
-
/** finishReason='error' 时的结构化原因(消费方落 turnEnd meta
|
|
89
|
+
/** finishReason='error' 时的结构化原因(消费方落 turnEnd meta,不进模型消息流);
|
|
90
|
+
* 1.23-W1(D-W1-2):+kind 失败细类(LlmErrorKind 注册表——B080001 一码多形态的分型标注) */
|
|
83
91
|
error?: {
|
|
84
92
|
code: string;
|
|
85
93
|
message: string;
|
|
94
|
+
kind?: LlmErrorKind;
|
|
86
95
|
};
|
|
87
96
|
/** 1.11-S1(D-33):每次请求最后一个 chunk 附带归一化用量;缺失 = provider 未回,禁补造 */
|
|
88
97
|
usage?: LlmUsage;
|
package/dist/sbom.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CAR-SBOM",
|
|
3
3
|
"specVersion": "0.1",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.24.0",
|
|
5
5
|
"components": [
|
|
6
6
|
{
|
|
7
7
|
"type": "file",
|
|
@@ -101,12 +101,17 @@
|
|
|
101
101
|
{
|
|
102
102
|
"type": "file",
|
|
103
103
|
"path": "src/loop/recover.ts",
|
|
104
|
-
"sha256": "
|
|
104
|
+
"sha256": "28f3ee3c244380b91ea5f037bb7920c55c2979ea5e818f77827ab2c473e1578a"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"type": "file",
|
|
108
|
+
"path": "src/loop/settlement.ts",
|
|
109
|
+
"sha256": "07b9da93f216f5bee4dc7e034e7e194e9a209449599e71371255c7cd388f4c3a"
|
|
105
110
|
},
|
|
106
111
|
{
|
|
107
112
|
"type": "file",
|
|
108
113
|
"path": "src/loop/stop.ts",
|
|
109
|
-
"sha256": "
|
|
114
|
+
"sha256": "7e185d31c4c65ee4ba1d43135a609572e677a70f93110c183e34d593d8aaf0a6"
|
|
110
115
|
},
|
|
111
116
|
{
|
|
112
117
|
"type": "file",
|
|
@@ -161,7 +166,7 @@
|
|
|
161
166
|
{
|
|
162
167
|
"type": "file",
|
|
163
168
|
"path": "src/runtime-core/chatStep.ts",
|
|
164
|
-
"sha256": "
|
|
169
|
+
"sha256": "c95f71f3fbe5a99b2fe02d3a71450efb2bb31b003a30da6eafc39eb57c791f33"
|
|
165
170
|
},
|
|
166
171
|
{
|
|
167
172
|
"type": "file",
|
|
@@ -171,12 +176,12 @@
|
|
|
171
176
|
{
|
|
172
177
|
"type": "file",
|
|
173
178
|
"path": "src/runtime-core/errors.ts",
|
|
174
|
-
"sha256": "
|
|
179
|
+
"sha256": "b7611e5a1647bb0022a76b9f8211a59f8097c0d70c4747d56600e122ca5198a8"
|
|
175
180
|
},
|
|
176
181
|
{
|
|
177
182
|
"type": "file",
|
|
178
183
|
"path": "src/runtime-core/llm.ts",
|
|
179
|
-
"sha256": "
|
|
184
|
+
"sha256": "fcab344c1dae5a4fac24dcb8e7be3a82cf0442411b4b9943e558a07f35e86a30"
|
|
180
185
|
},
|
|
181
186
|
{
|
|
182
187
|
"type": "file",
|
|
@@ -201,7 +206,7 @@
|
|
|
201
206
|
{
|
|
202
207
|
"type": "file",
|
|
203
208
|
"path": "src/runtime-core/types.ts",
|
|
204
|
-
"sha256": "
|
|
209
|
+
"sha256": "c11f7fbb38ff1f5567e7a18c87300f3bc3bf802b7d3660ebb30a8191017ad37a"
|
|
205
210
|
},
|
|
206
211
|
{
|
|
207
212
|
"type": "file",
|
|
@@ -251,7 +256,7 @@
|
|
|
251
256
|
{
|
|
252
257
|
"type": "file",
|
|
253
258
|
"path": "src/session/log.ts",
|
|
254
|
-
"sha256": "
|
|
259
|
+
"sha256": "7cecb0ef745d27bd5c1a8935b19f2f2b54546cab0b0865c29b0826aad32bcd11"
|
|
255
260
|
},
|
|
256
261
|
{
|
|
257
262
|
"type": "file",
|
|
@@ -271,7 +276,7 @@
|
|
|
271
276
|
{
|
|
272
277
|
"type": "file",
|
|
273
278
|
"path": "package.json",
|
|
274
|
-
"sha256": "
|
|
279
|
+
"sha256": "4c74e9d524e6d57a89b85936fc67b7d454a947c22686d87a0d7a09a1afade230"
|
|
275
280
|
},
|
|
276
281
|
{
|
|
277
282
|
"type": "file",
|
package/dist/session/log.js
CHANGED
|
@@ -132,6 +132,11 @@ export class SessionLog {
|
|
|
132
132
|
let pendingThinkingTurn = null;
|
|
133
133
|
let pendingBlocks = [];
|
|
134
134
|
for (const e of projected) {
|
|
135
|
+
// 1.24-W2(D-W2-1):settlement 回执不投影(model_visible=0 同 usage 纪律——结算凭据是
|
|
136
|
+
// 审计面事实非模型上下文;system kind 语义位但 payload.type 判别排除,先于 thinking
|
|
137
|
+
// 隔断检查,杜绝任何投影面影响;无 settlement 链投影逐字节不变)
|
|
138
|
+
if (e.kind === 'system' && e.payload?.type === 'settlement')
|
|
139
|
+
continue;
|
|
135
140
|
if (pendingBlocks.length && (e.turnId !== pendingThinkingTurn || e.kind === 'user' || e.kind === 'system'))
|
|
136
141
|
pendingBlocks = [];
|
|
137
142
|
if (e.kind === 'thinking') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lqc123qwe/car-runtime",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.0",
|
|
4
4
|
"description": "Composable Agent Runtime (CAR) - microkernel plugin system with sandboxed PTC, append-only hashed session log, and MCP host bridge",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/loop/recover.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* - 副作用审计:补记事件带 recovered=true 标记(副作用是否落地由恢复层显式声明为未知)
|
|
8
8
|
*/
|
|
9
9
|
import type { SessionLog, SessionEvent } from '../session/log.ts'
|
|
10
|
+
import { appendSettlement } from './settlement.ts'
|
|
10
11
|
|
|
11
12
|
export interface RecoveryReport {
|
|
12
13
|
/** 补记 interrupted 的 turn 数 */
|
|
@@ -39,7 +40,10 @@ export function recoverInterrupted(log: SessionLog): RecoveryReport {
|
|
|
39
40
|
report.pairedCalls++
|
|
40
41
|
}
|
|
41
42
|
// 未闭合 turn → 补记 turnEnd=interrupted(不可与 aborted 混用)
|
|
43
|
+
// 1.24-W2(D-W2-2):补记前先落 settlement——部分链对账 = hasUnknownOutcomes 主战场
|
|
44
|
+
// (上方成对补记的 recovered:true 合成结果在结算里按「结果不明」显式记账,不丢信号)
|
|
42
45
|
for (const turnId of openTurns) {
|
|
46
|
+
appendSettlement(log, turnId, { reason: 'interrupted', recovered: true })
|
|
43
47
|
log.append('runtime', 'turnEnd', turnId, null, { reason: 'interrupted', recovered: true })
|
|
44
48
|
report.turnsClosed++
|
|
45
49
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 1.24-W2 · 回合结算回执(settlement receipt)
|
|
3
|
+
*
|
|
4
|
+
* 口径(《Qwen-Code 对照分析》§2.3/§5 / 借鉴四篇裁决记录 Q-W2 / 迭代规划 D-W2-1~6):
|
|
5
|
+
* - 「结算必留回执」:任何 turn 无论成败都有一张结构化凭据——观测 / 恢复 / 审计消费同一张
|
|
6
|
+
* - 落链形态 = kind:'system' + payload.type:'settlement'(EventKind 零新增;payload.type 扩展
|
|
7
|
+
* 沿 routing 'decide' / memory 'write|edit|delete|rollback' 先例);一 turn 一张,llm 面与
|
|
8
|
+
* tool 节合并(D-W2-1)
|
|
9
|
+
* - 产出点仅两处:runTurn closeTurn(turnEnd 之前——turnEnd 保持终止标记语义)与
|
|
10
|
+
* recoverInterrupted 补记(部分链对账);authz 台账 turnEnd 与 facade sessionStop 不经
|
|
11
|
+
* closeTurn、不触发(D-W2-2 登记)
|
|
12
|
+
* - 事实源 = 链(非运行时状态):两本账按链上 toolCall/toolResult payload.id 对账;
|
|
13
|
+
* permissionDenialCount = toolResult error:'authorization-denied' 且 granted===false;
|
|
14
|
+
* hasUnknownOutcomes = 无配对 toolResult 或配对结果 recovered:true(副作用状态未知显式记账);
|
|
15
|
+
* bounded = aborted-before-dispatch 补记形态在链(取消落在派发边界,未打断在途执行)
|
|
16
|
+
* - 字段 CAR 化:outcome 由 reason 映射(complete|interrupted|failed);failureKind = 1.23
|
|
17
|
+
* meta.kind 诚实缺键;assistantSeq 代 Qwen assistantMessageUuid;Qwen nextMaxOutputTokens
|
|
18
|
+
* 在 CAR 无对应机制 → 诚实缺键不预埋(D-W2-3 偏差登记)
|
|
19
|
+
* - turnEnd meta 零变化:settlement 是独立事件非 meta 尾键(D-W2-4,s78-16 字节兼容锚不动);
|
|
20
|
+
* stopRequested 经 closeTurn hints 传递不落 meta
|
|
21
|
+
* - kill-switch:CAR_SETTLEMENT=off 跳过 append(K1 env 逐 turn 求值先例);缺省开(D-W2-5)
|
|
22
|
+
* - 链量纪律(R-W2):定长小 payload——账本数组 cap 64 + truncated 标记;内容类信息不进链
|
|
23
|
+
*/
|
|
24
|
+
import type { SessionLog } from '../session/log.ts'
|
|
25
|
+
|
|
26
|
+
export type SettlementOutcome = 'complete' | 'interrupted' | 'failed'
|
|
27
|
+
|
|
28
|
+
/** 账本数组上限(超出截断 + ledgerTruncated 标记——防病态长 turn 链量失控) */
|
|
29
|
+
export const SETTLEMENT_LEDGER_CAP = 64
|
|
30
|
+
|
|
31
|
+
export interface SettlementHints {
|
|
32
|
+
/** terminate 型收口(工具主动 stop 语义,stop.ts closeTurn hints——不落 turnEnd meta) */
|
|
33
|
+
stopRequested?: boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** TurnEndReason → settlement outcome 映射(completed/max-tokens=complete;aborted/interrupted=interrupted;error/blocked=failed) */
|
|
37
|
+
export function outcomeFromReason(reason: string): SettlementOutcome {
|
|
38
|
+
if (reason === 'completed' || reason === 'max-tokens') return 'complete'
|
|
39
|
+
if (reason === 'aborted' || reason === 'interrupted') return 'interrupted'
|
|
40
|
+
return 'failed'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 结算回执落链(closeTurn / recover 共用单点)。meta = 同 turn turnEnd 将携带的 meta
|
|
45
|
+
* (reason/kind 等事实同源);usage/hints 缺省时从链自证(requestSent 由 usage 事件或
|
|
46
|
+
* assistant 在场推导)。
|
|
47
|
+
*/
|
|
48
|
+
export function appendSettlement(
|
|
49
|
+
log: SessionLog,
|
|
50
|
+
turnId: string,
|
|
51
|
+
meta: Record<string, unknown>,
|
|
52
|
+
hints?: SettlementHints,
|
|
53
|
+
): void {
|
|
54
|
+
if (process.env.CAR_SETTLEMENT === 'off') return
|
|
55
|
+
const reason = typeof meta.reason === 'string' ? meta.reason : 'error'
|
|
56
|
+
const kind = typeof meta.kind === 'string' ? meta.kind : undefined
|
|
57
|
+
|
|
58
|
+
// 单次链扫描:两本账 + 拒绝计数 + unknown/bounded 事实 + requestSent 事实 + assistantSeq
|
|
59
|
+
const declared: string[] = []
|
|
60
|
+
const resultById = new Map<string, Record<string, unknown>>()
|
|
61
|
+
let denialCount = 0
|
|
62
|
+
let bounded = false
|
|
63
|
+
let usageRequests = 0
|
|
64
|
+
let assistantSeq: number | undefined
|
|
65
|
+
for (const e of log.events) {
|
|
66
|
+
if (e.turnId !== turnId) continue
|
|
67
|
+
const p = (e.payload ?? {}) as Record<string, unknown>
|
|
68
|
+
if (e.kind === 'toolCall') {
|
|
69
|
+
if (typeof p.id === 'string') declared.push(p.id)
|
|
70
|
+
} else if (e.kind === 'toolResult') {
|
|
71
|
+
if (typeof p.id === 'string') {
|
|
72
|
+
resultById.set(p.id, p) // 1.6-S1 last-wins 同款口径
|
|
73
|
+
if (p.error === 'authorization-denied' && p.granted === false) denialCount++
|
|
74
|
+
if (p.error === 'aborted-before-dispatch') bounded = true
|
|
75
|
+
}
|
|
76
|
+
} else if (e.kind === 'usage') {
|
|
77
|
+
usageRequests++
|
|
78
|
+
} else if (e.kind === 'assistant') {
|
|
79
|
+
assistantSeq = e.seq
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const dispatched = [...resultById.keys()]
|
|
83
|
+
const unknownIds = declared.filter((id) => {
|
|
84
|
+
const res = resultById.get(id)
|
|
85
|
+
return !res || res.recovered === true
|
|
86
|
+
})
|
|
87
|
+
const cap = (arr: string[]) => (arr.length > SETTLEMENT_LEDGER_CAP ? arr.slice(0, SETTLEMENT_LEDGER_CAP) : arr)
|
|
88
|
+
const ledgerTruncated = declared.length > SETTLEMENT_LEDGER_CAP || dispatched.length > SETTLEMENT_LEDGER_CAP
|
|
89
|
+
|
|
90
|
+
log.append('runtime', 'system', turnId, {
|
|
91
|
+
type: 'settlement',
|
|
92
|
+
outcome: outcomeFromReason(reason),
|
|
93
|
+
...(kind ? { failureKind: kind } : {}),
|
|
94
|
+
requestSent: usageRequests > 0 || assistantSeq !== undefined,
|
|
95
|
+
...(assistantSeq !== undefined ? { assistantSeq } : {}),
|
|
96
|
+
declaredToolUseIds: cap(declared),
|
|
97
|
+
dispatchedToolUseIds: cap(dispatched),
|
|
98
|
+
...(ledgerTruncated ? { ledgerTruncated: true } : {}),
|
|
99
|
+
tool: {
|
|
100
|
+
permissionDenialCount: denialCount,
|
|
101
|
+
hasUnknownOutcomes: unknownIds.length > 0,
|
|
102
|
+
...(unknownIds.length ? { unknownToolUseIds: cap(unknownIds) } : {}),
|
|
103
|
+
stopRequested: hints?.stopRequested === true,
|
|
104
|
+
bounded,
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
}
|
package/src/loop/stop.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { SessionLog } from '../session/log.ts'
|
|
18
18
|
import { CarM8Error } from '../runtime-core/errors.ts'
|
|
19
|
+
import { appendSettlement } from './settlement.ts'
|
|
19
20
|
|
|
20
21
|
export type StepOutcome = 'completed' | 'max-tokens' | 'null'
|
|
21
22
|
export type TurnEndReason = 'completed' | 'max-tokens' | 'aborted' | 'error' | 'blocked' | 'interrupted'
|
|
@@ -74,8 +75,14 @@ export interface TurnPreset {
|
|
|
74
75
|
|
|
75
76
|
export interface TurnResult { reason: TurnEndReason; steps: number; endReasonTrail: Array<{ seq: number; reason: TurnEndReason }> }
|
|
76
77
|
|
|
78
|
+
/**
|
|
79
|
+
* 1.23-W1(D-W1-6):取消时机标注(W-0 审计 F-W0-3——aborted 三路径链上不区分)——
|
|
80
|
+
* 'before-request' = 请求未发出(runTurn 循环头 / chatStep 零 chunk 流结束检查);
|
|
81
|
+
* 'in-flight' = 在途打断(chatStep chunk 间隙 / 有 chunk 后的流结束检查)。
|
|
82
|
+
* 缺省缺席 = 1.22 形态(terminate 型 abort 为工具主动收口语义,非取消,不带键)。
|
|
83
|
+
*/
|
|
77
84
|
export class TurnAborted extends Error {
|
|
78
|
-
constructor() { super('CAR-ABORTED: turn cancelled by user') }
|
|
85
|
+
constructor(readonly abortedAt?: 'before-request' | 'in-flight') { super('CAR-ABORTED: turn cancelled by user') }
|
|
79
86
|
}
|
|
80
87
|
|
|
81
88
|
/**
|
|
@@ -123,13 +130,16 @@ export async function runTurn(opts: {
|
|
|
123
130
|
|
|
124
131
|
// 1.11-S2(D-33):turnEnd 落链单点包装——聚合本 turn usage 事件(无 usage 不带键,
|
|
125
132
|
// 缺省=未采集向后兼容);reason 保持 meta 首键,usage 恒为加法尾键
|
|
126
|
-
|
|
133
|
+
// 1.24-W2(D-W2-2):turnEnd 之前先落 settlement 结算回执(turnEnd 保持终止标记语义;
|
|
134
|
+
// hints 仅 terminate 收口传递 stopRequested——不落 turnEnd meta,D-W2-4)
|
|
135
|
+
const closeTurn = (meta: Record<string, unknown>, hints?: { stopRequested?: boolean }) => {
|
|
127
136
|
const usage = turnUsageFrom(log, turnId)
|
|
128
137
|
// 1.18.2-K8(R-K7-2):计量缺失 fail-visible——商汤网关实测 20% 概率 message_delta
|
|
129
138
|
// 不带 usage(流正常回复但计量整条缺席)→ 缺失口径纪律不落半条事实(turnUsageFrom
|
|
130
139
|
// undefined),缺失本身在此显式标注:本 turn 存在 assistant 事件(模型有可见输出 ⇒
|
|
131
140
|
// 必有请求)而无 usage 聚合时带 usageMissing: true(加法尾键,与 usage 同语义位)
|
|
132
141
|
const hasAssistant = log.events.some((e) => e.kind === 'assistant' && e.turnId === turnId)
|
|
142
|
+
appendSettlement(log, turnId, meta, hints)
|
|
133
143
|
return log.append('runtime', 'turnEnd', turnId, null, {
|
|
134
144
|
...meta,
|
|
135
145
|
...(usage ? { usage } : hasAssistant ? { usageMissing: true } : {}),
|
|
@@ -231,7 +241,8 @@ export async function runTurn(opts: {
|
|
|
231
241
|
try {
|
|
232
242
|
while (steps < maxSteps) {
|
|
233
243
|
if (signal?.aborted) {
|
|
234
|
-
|
|
244
|
+
// 1.23-W1(D-W1-6):循环头取消 = 请求未发出(无在途请求)
|
|
245
|
+
const seq = closeTurn({ reason: 'aborted', abortedAt: 'before-request' }).seq
|
|
235
246
|
trail.push({ seq, reason: 'aborted' })
|
|
236
247
|
return { reason: 'aborted', steps, endReasonTrail: trail }
|
|
237
248
|
}
|
|
@@ -294,7 +305,8 @@ export async function runTurn(opts: {
|
|
|
294
305
|
const agg = preset.aggregate ?? {}
|
|
295
306
|
const abortedNow = (agg.terminate ?? 'all') === 'any' ? batch.terminateVotes > 0 : batch.terminateAll
|
|
296
307
|
if (abortedNow) { // terminate 收口:默认 AND(整批),可配置 any
|
|
297
|
-
|
|
308
|
+
// 1.24-W2:工具主动 stop 语义 → settlement stopRequested=true(hints 不落 meta,D-W2-4)
|
|
309
|
+
const seq = closeTurn({ reason: 'aborted' }, { stopRequested: true }).seq
|
|
298
310
|
trail.push({ seq, reason: 'aborted' })
|
|
299
311
|
return { reason: 'aborted', steps, endReasonTrail: trail }
|
|
300
312
|
}
|
|
@@ -320,17 +332,19 @@ export async function runTurn(opts: {
|
|
|
320
332
|
return { reason: 'error', steps, endReasonTrail: trail }
|
|
321
333
|
} catch (e) {
|
|
322
334
|
if (e instanceof TurnAborted) {
|
|
323
|
-
|
|
335
|
+
// 1.23-W1(D-W1-6):chatStep 甄别过的取消时机随异常携带(terminate 型 abort 不经此路径)
|
|
336
|
+
const seq = closeTurn({ reason: 'aborted', ...(e.abortedAt ? { abortedAt: e.abortedAt } : {}) }).seq
|
|
324
337
|
trail.push({ seq, reason: 'aborted' })
|
|
325
338
|
return { reason: 'aborted', steps, endReasonTrail: trail }
|
|
326
339
|
}
|
|
327
340
|
// 1.4-S4:CarM8Error 保形——code/userHint/retryable 进 turnEnd(A080001 引导文案「请运行
|
|
328
|
-
// car doctor」可达用户;非 CarM8Error 维持 String(e)
|
|
341
|
+
// car doctor」可达用户;非 CarM8Error 维持 String(e) 口径不变);
|
|
342
|
+
// 1.23-W1(D-W1-3):+kind 失败细类(加法尾键——B080001 多形态分型,审计 F-W0-2)
|
|
329
343
|
const m8 = e instanceof CarM8Error
|
|
330
344
|
const detail = m8 ? `${e.slug}: ${e.message}` : String(e)
|
|
331
345
|
const seq = closeTurn({
|
|
332
346
|
reason: 'error', detail,
|
|
333
|
-
...(m8 ? { code: e.code, userHint: e.userHint, retryable: e.retryable } : {}),
|
|
347
|
+
...(m8 ? { code: e.code, userHint: e.userHint, retryable: e.retryable, ...(e.kind ? { kind: e.kind } : {}) } : {}),
|
|
334
348
|
}).seq
|
|
335
349
|
trail.push({ seq, reason: 'error' })
|
|
336
350
|
return { reason: 'error', steps, endReasonTrail: trail }
|
|
@@ -23,7 +23,7 @@ import type { RuntimeCore } from './llm.ts'
|
|
|
23
23
|
import type { LlmUsage, ToolDefinition } from './types.ts'
|
|
24
24
|
import { StreamRedactor } from './redaction.ts'
|
|
25
25
|
import { scanSecrets } from '../security/secrets.ts'
|
|
26
|
-
import { providerUnreachable } from './errors.ts'
|
|
26
|
+
import { CarM8Error, providerUnreachable } from './errors.ts'
|
|
27
27
|
|
|
28
28
|
export interface ChatStepOptions {
|
|
29
29
|
core: RuntimeCore
|
|
@@ -119,6 +119,12 @@ export async function chatStep(opts: ChatStepOptions): Promise<ChatStepResult> {
|
|
|
119
119
|
const thinkingRedactor = new StreamRedactor()
|
|
120
120
|
let stopReason: ModelStep['stopReason'] | undefined
|
|
121
121
|
let errorDetail: string | undefined
|
|
122
|
+
// 1.23-W1(D-W1-2):error chunk 细类捕获(最后一条 error chunk 的 kind——providerUnreachable
|
|
123
|
+
// 携带 → runTurn turnEnd meta.kind;缺失 = 1.22 形态)
|
|
124
|
+
let errorKind: string | undefined
|
|
125
|
+
// 1.23-W1(D-W1-6):零 chunk 甄别——流结束检查抛 TurnAborted 时区分「请求未发出(attempt 前
|
|
126
|
+
// 静默收口)」与「在途打断」;chunk 间隙路径恒 in-flight
|
|
127
|
+
let sawChunk = false
|
|
122
128
|
let usage: LlmUsage | undefined
|
|
123
129
|
|
|
124
130
|
const stream = core.chat({
|
|
@@ -133,8 +139,10 @@ export async function chatStep(opts: ChatStepOptions): Promise<ChatStepResult> {
|
|
|
133
139
|
...(opts.log.sessionId ? { metadata: { sessionId: opts.log.sessionId, turnId, traceId: `${opts.log.sessionId}:${turnId}` } } : {}),
|
|
134
140
|
})
|
|
135
141
|
for await (const chunk of stream) {
|
|
136
|
-
|
|
137
|
-
|
|
142
|
+
sawChunk = true
|
|
143
|
+
// 1.4-S4(D-19):chunk 间隙取消——此刻 assistant 尚未落链(流完才 append),无半截消息;
|
|
144
|
+
// 1.23-W1(D-W1-6):在途打断标注 in-flight
|
|
145
|
+
if (opts.signal?.aborted) throw new TurnAborted('in-flight')
|
|
138
146
|
if (chunk.delta !== undefined) {
|
|
139
147
|
const masked = redactor.push(chunk.delta)
|
|
140
148
|
text += masked
|
|
@@ -171,13 +179,18 @@ export async function chatStep(opts: ChatStepOptions): Promise<ChatStepResult> {
|
|
|
171
179
|
if (masked) opts.onDelta({ thinkingDelta: masked })
|
|
172
180
|
}
|
|
173
181
|
}
|
|
174
|
-
if (chunk.error)
|
|
182
|
+
if (chunk.error) {
|
|
183
|
+
errorDetail = chunk.error.message
|
|
184
|
+
errorKind = chunk.error.kind // 1.23-W1(D-W1-2):细类透传(最后一条 error chunk 为准)
|
|
185
|
+
}
|
|
175
186
|
if (chunk.usage) usage = chunk.usage // D-33:附着于最后一个 chunk——后到覆盖
|
|
176
187
|
if (chunk.finishReason !== undefined) stopReason = chunk.finishReason === 'aborted' ? 'error' : chunk.finishReason
|
|
177
188
|
}
|
|
178
189
|
// 1.4-S4(D-19):finish 检查前先查取消(适配器取消路径静默收口无 finishReason——
|
|
179
|
-
//
|
|
180
|
-
|
|
190
|
+
// 必须先于此处的契约兜底,否则取消被误判为适配器违规);
|
|
191
|
+
// 1.23-W1(D-W1-6):abortedAt 甄别——有 chunk = 在途打断(in-flight);
|
|
192
|
+
// 零 chunk = attempt 前静默收口,请求未发出(before-request)
|
|
193
|
+
if (opts.signal?.aborted) throw new TurnAborted(sawChunk ? 'in-flight' : 'before-request')
|
|
181
194
|
if (stopReason === undefined) {
|
|
182
195
|
// 适配器层已保证首 finishReason(AL-05);防御性兜底显式化
|
|
183
196
|
throw new Error('CAR-E-LLM-FINISH: 流结束无 finishReason(适配器契约违规)')
|
|
@@ -256,16 +269,26 @@ export async function chatStep(opts: ChatStepOptions): Promise<ChatStepResult> {
|
|
|
256
269
|
if (!c.name) continue
|
|
257
270
|
let args: Record<string, unknown>
|
|
258
271
|
try { args = c.argsBuf ? JSON.parse(c.argsBuf) as Record<string, unknown> : {} } catch {
|
|
259
|
-
// toolUse 声明下 args 非法 JSON = 协议违规显式化(非 length 场景无 truncatedTools
|
|
260
|
-
|
|
272
|
+
// toolUse 声明下 args 非法 JSON = 协议违规显式化(非 length 场景无 truncatedTools 口径);
|
|
273
|
+
// 1.23-W1(D-W1-3):转 CarM8Error——turnEnd meta 获得 code/userHint/retryable + kind
|
|
274
|
+
// 'proto-invalid-args'(detail 形态变更登记:CarM8Error 统一格式带(B080001)码后缀)
|
|
275
|
+
throw new CarM8Error(
|
|
276
|
+
'B080001',
|
|
277
|
+
'CAR-E-LLM-PROTO',
|
|
278
|
+
`工具调用 ${c.name} 参数非法 JSON(finishReason=${stopReason},${errorDetail ?? '无附加错误'})`,
|
|
279
|
+
undefined,
|
|
280
|
+
false,
|
|
281
|
+
'proto-invalid-args',
|
|
282
|
+
)
|
|
261
283
|
}
|
|
262
284
|
toolCalls.push({ id: c.id ?? `tc-${idx}-${c.name}`, tool: c.name, args })
|
|
263
285
|
}
|
|
264
286
|
return { stopReason: 'toolUse', ...(text ? { text } : {}), ...(toolCalls.length ? { toolCalls } : {}), secretsRedacted }
|
|
265
287
|
}
|
|
266
|
-
// error(含 aborted 映射):以 B080001 抛出 → runTurn catch → turnEnd error 携带 detail(BD-04
|
|
288
|
+
// error(含 aborted 映射):以 B080001 抛出 → runTurn catch → turnEnd error 携带 detail(BD-04 收口);
|
|
289
|
+
// 1.23-W1(D-W1-2):细类 kind 透传(error chunk 捕获的 errorKind;缺失 = 1.22 形态)
|
|
267
290
|
if (stopReason === 'error') {
|
|
268
|
-
throw providerUnreachable(errorDetail ?? 'provider 返回 error 终止(无附加说明)')
|
|
291
|
+
throw providerUnreachable(errorDetail ?? 'provider 返回 error 终止(无附加说明)', errorKind)
|
|
269
292
|
}
|
|
270
293
|
return { stopReason: 'stop', ...(text ? { text } : {}), secretsRedacted }
|
|
271
294
|
}
|
|
@@ -20,6 +20,12 @@ export class CarM8Error extends Error {
|
|
|
20
20
|
readonly userHint?: string,
|
|
21
21
|
/** 重试建议(A080001 不重试 / B080001 退避后重试) */
|
|
22
22
|
readonly retryable = false,
|
|
23
|
+
/**
|
|
24
|
+
* 1.23-W1(D-W1-3):失败细类(LlmErrorKind 注册表)——B080001 一码多形态的分型标注,
|
|
25
|
+
* 经 runTurn closeTurn 落 turnEnd meta.kind(加法尾键,消费面免字符串反解)。
|
|
26
|
+
* 缺省缺席 = 1.22 形态(A080001 凭据缺失以独立码区分,无需 kind)。
|
|
27
|
+
*/
|
|
28
|
+
readonly kind?: string,
|
|
23
29
|
) {
|
|
24
30
|
super(`${slug}: ${message}(${code})`)
|
|
25
31
|
this.name = 'CarM8Error'
|
|
@@ -36,12 +42,13 @@ export function credentialMissing(provider: string, searched: string[]): CarM8Er
|
|
|
36
42
|
)
|
|
37
43
|
}
|
|
38
44
|
|
|
39
|
-
export function providerUnreachable(detail: string): CarM8Error {
|
|
45
|
+
export function providerUnreachable(detail: string, kind?: string): CarM8Error {
|
|
40
46
|
return new CarM8Error(
|
|
41
47
|
'B080001',
|
|
42
48
|
'CAR-E-LLM-UNREACHABLE',
|
|
43
49
|
`LLM Provider 不可达/重试耗尽:${detail}`,
|
|
44
50
|
'模型服务暂时不可用,本轮已停止',
|
|
45
51
|
true,
|
|
52
|
+
kind,
|
|
46
53
|
)
|
|
47
54
|
}
|
package/src/runtime-core/llm.ts
CHANGED
|
@@ -244,7 +244,9 @@ async function* streamSsePost(spec: SsePostSpec, parse: (body: AsyncIterable<Uin
|
|
|
244
244
|
if (!res.ok) {
|
|
245
245
|
const text = await res.text().catch(() => '')
|
|
246
246
|
// 4xx 业务错不重试(§3.5.4);错误信息不含凭据(凭据值不进错误路径)
|
|
247
|
-
|
|
247
|
+
// 1.23-W1(D-W1-5):4xx 细类标注——overflow 特征命中 = context-limit-suspect(疑似),否则 transport-4xx
|
|
248
|
+
const kind = contextLimitSuspect(text) ? 'context-limit-suspect' : 'transport-4xx'
|
|
249
|
+
yield { finishReason: 'error', error: { code: 'B080001', message: `provider HTTP ${res.status}${text ? `:${text.slice(0, 200)}` : ''}`, kind } }
|
|
248
250
|
return
|
|
249
251
|
}
|
|
250
252
|
if (!res.body) throw new Retryable('响应无 body 流')
|
|
@@ -260,7 +262,8 @@ async function* streamSsePost(spec: SsePostSpec, parse: (body: AsyncIterable<Uin
|
|
|
260
262
|
// 流自然结束但未给 finishReason = 异常终止(AL-05 口径:缺失显式化,禁吞没);
|
|
261
263
|
// 已携带 finishReason 的流原样收口——兜底不得追加(M8-BUG-1 口径延续)
|
|
262
264
|
if (!sawFinish) {
|
|
263
|
-
|
|
265
|
+
// 1.23-W1:细类 no-finish-reason(审计 F-W0-2 拆型)
|
|
266
|
+
yield { finishReason: 'error', error: { code: 'B080001', message: '流结束未携带 finish_reason(AL-05)', kind: 'no-finish-reason' } }
|
|
264
267
|
}
|
|
265
268
|
return
|
|
266
269
|
} catch (e) {
|
|
@@ -274,11 +277,11 @@ async function* streamSsePost(spec: SsePostSpec, parse: (body: AsyncIterable<Uin
|
|
|
274
277
|
continue
|
|
275
278
|
}
|
|
276
279
|
if (beforeFirstByte) {
|
|
277
|
-
// 重试耗尽 → B080001(BD-04:消费方 runTurn 以 error
|
|
278
|
-
throw providerUnreachable(`${(e as Error).name}: ${(e as Error).message}(尝试 ${attempt + 1}/${spec.retries + 1},${Date.now() - started}ms
|
|
280
|
+
// 重试耗尽 → B080001(BD-04:消费方 runTurn 以 error 收口);1.23-W1 细类 transport-exhausted
|
|
281
|
+
throw providerUnreachable(`${(e as Error).name}: ${(e as Error).message}(尝试 ${attempt + 1}/${spec.retries + 1},${Date.now() - started}ms)`, 'transport-exhausted')
|
|
279
282
|
}
|
|
280
|
-
// 流中途失败:已消费 chunk 不可重放 → error chunk
|
|
281
|
-
yield { finishReason: 'error', error: { code: 'B080001', message: `流中途失败:${(e as Error).message}
|
|
283
|
+
// 流中途失败:已消费 chunk 不可重放 → error chunk 收口;1.23-W1 细类 stream-midbreak
|
|
284
|
+
yield { finishReason: 'error', error: { code: 'B080001', message: `流中途失败:${(e as Error).message}`, kind: 'stream-midbreak' } }
|
|
282
285
|
return
|
|
283
286
|
} finally {
|
|
284
287
|
clearTimeout(firstByteTimer)
|
|
@@ -317,6 +320,23 @@ const FINISH_MAP: Record<string, LlmChunk['finishReason']> = {
|
|
|
317
320
|
|
|
318
321
|
class Retryable extends Error {}
|
|
319
322
|
|
|
323
|
+
/**
|
|
324
|
+
* 1.23-W1(D-W1-5):context-limit-suspect 确定性关键词表——仅用于 4xx 错误体、只收高置信特征;
|
|
325
|
+
* 标注语义 = **疑似**(消费面文案须带「疑似」,禁估算禁 LLM 判断);未命中 = transport-4xx。
|
|
326
|
+
*/
|
|
327
|
+
const CONTEXT_LIMIT_PATTERNS: RegExp[] = [
|
|
328
|
+
/context length/i,
|
|
329
|
+
/maximum context/i,
|
|
330
|
+
/context[_ ]window/i,
|
|
331
|
+
/prompt is too long/i,
|
|
332
|
+
/too many tokens/i,
|
|
333
|
+
/input tokens? exceed/i,
|
|
334
|
+
/exceeds? the (maximum )?(context|token)/i,
|
|
335
|
+
/上下文(长度|超限)/,
|
|
336
|
+
/超出上下文/,
|
|
337
|
+
]
|
|
338
|
+
const contextLimitSuspect = (text: string): boolean => CONTEXT_LIMIT_PATTERNS.some((p) => p.test(text))
|
|
339
|
+
|
|
320
340
|
/**
|
|
321
341
|
* 1.11-S1(D-33 / 计量设计 §1.1):openai usage 归一——prompt_tokens 含 cached,inputTokens
|
|
322
342
|
* 必须做减法(prompt − prompt_tokens_details.cached_tokens);cache_creation 恒 0(无对应字段,
|
|
@@ -399,13 +419,21 @@ async function* parseSseStream(body: AsyncIterable<Uint8Array>, model: string, a
|
|
|
399
419
|
if (usage) yield { usage }
|
|
400
420
|
continue
|
|
401
421
|
}
|
|
422
|
+
// 1.23-W1(D-W1-4 / D-W1-1):finish 终态附带 error 细类——未映射 = unmapped-finish;
|
|
423
|
+
// content_filter(openai 族唯一映射 'aborted' 的信号)= refusal(与 anthropic stop_reason=refusal 对齐)
|
|
424
|
+
const finishError: LlmChunk['error'] | undefined =
|
|
425
|
+
finish === 'error'
|
|
426
|
+
? { code: 'B080001', message: `未映射的 finish_reason "${raw}"`, kind: 'unmapped-finish' }
|
|
427
|
+
: finish === 'aborted'
|
|
428
|
+
? { code: 'B080001', message: `provider content_filter (finish_reason="${raw}")`, kind: 'refusal' }
|
|
429
|
+
: undefined
|
|
402
430
|
const chunks: LlmChunk[] = []
|
|
403
431
|
if (toolCallDeltas.length === 0) {
|
|
404
432
|
chunks.push({
|
|
405
433
|
...(reasoningContent !== undefined ? { thinkingDelta: reasoningContent } : {}),
|
|
406
434
|
...(text !== undefined ? { delta: text } : {}),
|
|
407
435
|
...(finish !== undefined
|
|
408
|
-
? { finishReason: finish, ...(
|
|
436
|
+
? { finishReason: finish, ...(finishError ? { error: finishError } : {}) }
|
|
409
437
|
: {}),
|
|
410
438
|
})
|
|
411
439
|
} else {
|
|
@@ -416,7 +444,7 @@ async function* parseSseStream(body: AsyncIterable<Uint8Array>, model: string, a
|
|
|
416
444
|
...(i === 0 && text !== undefined ? { delta: text } : {}),
|
|
417
445
|
toolCallDelta: toolCallDeltas[i],
|
|
418
446
|
...(last && finish !== undefined
|
|
419
|
-
? { finishReason: finish, ...(
|
|
447
|
+
? { finishReason: finish, ...(finishError ? { error: finishError } : {}) }
|
|
420
448
|
: {}),
|
|
421
449
|
})
|
|
422
450
|
}
|
|
@@ -835,17 +863,22 @@ async function* parseAnthropicSseStream(body: AsyncIterable<Uint8Array>, model:
|
|
|
835
863
|
const usage = mergeAnthropicUsage(startUsage, (evt.usage ?? null) as Record<string, unknown> | null, model, adapterId)
|
|
836
864
|
if (raw != null) {
|
|
837
865
|
const finish = ANTHROPIC_STOP_REASON_MAP[String(raw)]
|
|
866
|
+
// 1.23-W1(D-W1-4 / D-W1-1):stop_reason 细类标注——refusal 附加 error chunk(kind 'refusal',
|
|
867
|
+
// 与 openai content_filter 对齐;finishReason 映射不动);未映射 = unmapped-finish
|
|
838
868
|
const chunk: LlmChunk = finish
|
|
839
|
-
?
|
|
840
|
-
|
|
869
|
+
? finish === 'aborted'
|
|
870
|
+
? { finishReason: finish, error: { code: 'B080001', message: `provider refusal (stop_reason="${raw}")`, kind: 'refusal' } }
|
|
871
|
+
: { finishReason: finish }
|
|
872
|
+
: { finishReason: 'error', error: { code: 'B080001', message: `未映射的 stop_reason "${raw}"`, kind: 'unmapped-finish' } }
|
|
841
873
|
yield usage ? { ...chunk, usage } : chunk
|
|
842
874
|
} else if (usage) {
|
|
843
875
|
// delta 未携 stop_reason 但携 usage(网关形态):usage 不随缺失终态信号丢失
|
|
844
876
|
yield { usage }
|
|
845
877
|
}
|
|
846
878
|
} else if (type === 'error') {
|
|
847
|
-
// 流内 error 事件(overloaded_error 等)→ error chunk 收口(后续 message_delta
|
|
848
|
-
|
|
879
|
+
// 流内 error 事件(overloaded_error 等)→ error chunk 收口(后续 message_delta 由守卫拦冲突);
|
|
880
|
+
// 1.23-W1 细类 provider-stream-error
|
|
881
|
+
yield { finishReason: 'error', error: { code: 'B080001', message: `provider 流错误:${evt.error?.message ?? JSON.stringify(evt).slice(0, 200)}`, kind: 'provider-stream-error' } }
|
|
849
882
|
}
|
|
850
883
|
}
|
|
851
884
|
}
|
|
@@ -40,6 +40,23 @@ export interface ToolCallDelta {
|
|
|
40
40
|
argumentsDelta?: string
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* 1.23-W1(D-W1-1 / 《W-0 失败分类学对照审计》§5):LLM 失败细类注册表——把 B080001 下压着的
|
|
45
|
+
* 多形态失败拆到可对应处置动作的粒度(审计 F-W0-2)。**加法演进**:可选键,缺失 = 1.22 形态
|
|
46
|
+
* 逐字节兼容。`transport-network` 从审计 8 类剔除(无独立产出点——网络失败只以 exhausted /
|
|
47
|
+
* midbreak 到达消费面);`unmapped-finish` / `provider-stream-error` 为实施中浮出的两形态增补。
|
|
48
|
+
*/
|
|
49
|
+
export type LlmErrorKind =
|
|
50
|
+
| 'transport-4xx'
|
|
51
|
+
| 'transport-exhausted'
|
|
52
|
+
| 'stream-midbreak'
|
|
53
|
+
| 'no-finish-reason'
|
|
54
|
+
| 'refusal'
|
|
55
|
+
| 'proto-invalid-args'
|
|
56
|
+
| 'context-limit-suspect'
|
|
57
|
+
| 'unmapped-finish'
|
|
58
|
+
| 'provider-stream-error'
|
|
59
|
+
|
|
43
60
|
/**
|
|
44
61
|
* 1.11-S1(D-33 / 计量设计 §3.1):token 四分量归一化用量——跨 provider 可加总的库内口径
|
|
45
62
|
* (§1.1:openai prompt_tokens 含 cached 必须减法归一;anthropic input_tokens 天然不含 cache)。
|
|
@@ -72,8 +89,9 @@ export interface LlmChunk {
|
|
|
72
89
|
toolCallDelta?: ToolCallDelta
|
|
73
90
|
finishReason?: 'stop' | 'length' | 'toolUse' | 'error' | 'aborted'
|
|
74
91
|
// —— 规格外加法扩展(登记:M8 收口文档 §4 / 计量设计 §3.1,1.11.0)——
|
|
75
|
-
/** finishReason='error' 时的结构化原因(消费方落 turnEnd meta
|
|
76
|
-
|
|
92
|
+
/** finishReason='error' 时的结构化原因(消费方落 turnEnd meta,不进模型消息流);
|
|
93
|
+
* 1.23-W1(D-W1-2):+kind 失败细类(LlmErrorKind 注册表——B080001 一码多形态的分型标注) */
|
|
94
|
+
error?: { code: string; message: string; kind?: LlmErrorKind }
|
|
77
95
|
/** 1.11-S1(D-33):每次请求最后一个 chunk 附带归一化用量;缺失 = provider 未回,禁补造 */
|
|
78
96
|
usage?: LlmUsage
|
|
79
97
|
// —— 1.21(R-TOKENHUB-1 / D-T1)thinking 捕获加法——anthropic SSE thinking 面捕获;
|
package/src/session/log.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { createHash } from 'node:crypto'
|
|
|
11
11
|
import { readFileSync } from 'node:fs'
|
|
12
12
|
import { decodeLogBuffer, splitJsonlLines } from './format.ts'
|
|
13
13
|
|
|
14
|
-
export type EventKind = 'user' | 'assistant' | 'toolCall' | 'toolResult' | 'turnEnd' | 'goalUpdate' | 'hostRaw' | 'fork' | 'system' | 'usage' | 'compact' | 'routing' | 'memory' | 'thinking' // M3-S11 加法扩展:多宿主归一化降级通道(pattern 未命中留痕,零静默);M5-DEC4 加法扩展:跨宿主 fork 标记事件;1.4-S2 加法扩展:system prompt 落链(§3.5.5:新模型可见输入 = 新事件类型);1.11-S2 加法扩展:usage 计量事实(D-33,actor='runtime'、model_visible=0 不进模型消息流,deriveMessages 不投影);六值 TurnEndReason 不变;1.14-S2 加法扩展(K-MP-2 / D-40):历史压缩事件(actor='runtime',append-only 只追加——payload={summary, compactedUpTo, ...},投影按 compactedUpTo 前缀折叠为摘要,原事件链不改写);1.14-S4 加法扩展(K-POOL-1 / D-42):模型池路由决策(actor='runtime',payload.type='decide',selected/reason/retries/attempts——禁静默换模型;model_visible=0 不进模型消息流,deriveMessages 不投影——同 usage 纪律);1.15-S1 加法扩展(K-MEM-1 / D-46):白盒记忆操作事件(actor='user' 宿主代理用户操作,turnId='memory' 会话级标识同 'compaction' 先例,payload.type='write'|'edit'|'delete'|'rollback'——审计链同源差异化面,链即版本库〔rollback 恢复数据源=链上 payload.before/after 全文〕;model_visible=0 不进模型消息流——记忆注入走宿主装配层 system ctx 行,deriveMessages 不投影);1.21 加法扩展(R-TOKENHUB-1 / D-T2):thinking 事件(actor='model',payload=thinking 原文禁改写,meta={signature?, redacted?, secretsRedacted?}——先于 assistant/toolCall 落链;投影 = 同 turn 首个 toolCall 消息附加回传(工具环 reasoning_content 契约,Anthropic thinking+tool_use 规范同款);standalone/隔断/跨 turn 不投影;无 thinking 事件链投影逐字节不变;1.22 加法扩展(1.22 迭代规划 D-B1):多块语义——一块一事件(多块链每事件 meta.blockIndex=块序,单块链 meta 与 1.21 逐字节同形),投影单块 = 1.21 三元组、多块 = blocks 数组(D-B2,消费方 toAnthropic
|
|
14
|
+
export type EventKind = 'user' | 'assistant' | 'toolCall' | 'toolResult' | 'turnEnd' | 'goalUpdate' | 'hostRaw' | 'fork' | 'system' | 'usage' | 'compact' | 'routing' | 'memory' | 'thinking' // M3-S11 加法扩展:多宿主归一化降级通道(pattern 未命中留痕,零静默);M5-DEC4 加法扩展:跨宿主 fork 标记事件;1.4-S2 加法扩展:system prompt 落链(§3.5.5:新模型可见输入 = 新事件类型);1.11-S2 加法扩展:usage 计量事实(D-33,actor='runtime'、model_visible=0 不进模型消息流,deriveMessages 不投影);六值 TurnEndReason 不变;1.14-S2 加法扩展(K-MP-2 / D-40):历史压缩事件(actor='runtime',append-only 只追加——payload={summary, compactedUpTo, ...},投影按 compactedUpTo 前缀折叠为摘要,原事件链不改写);1.14-S4 加法扩展(K-POOL-1 / D-42):模型池路由决策(actor='runtime',payload.type='decide',selected/reason/retries/attempts——禁静默换模型;model_visible=0 不进模型消息流,deriveMessages 不投影——同 usage 纪律);1.15-S1 加法扩展(K-MEM-1 / D-46):白盒记忆操作事件(actor='user' 宿主代理用户操作,turnId='memory' 会话级标识同 'compaction' 先例,payload.type='write'|'edit'|'delete'|'rollback'——审计链同源差异化面,链即版本库〔rollback 恢复数据源=链上 payload.before/after 全文〕;model_visible=0 不进模型消息流——记忆注入走宿主装配层 system ctx 行,deriveMessages 不投影);1.21 加法扩展(R-TOKENHUB-1 / D-T2):thinking 事件(actor='model',payload=thinking 原文禁改写,meta={signature?, redacted?, secretsRedacted?}——先于 assistant/toolCall 落链;投影 = 同 turn 首个 toolCall 消息附加回传(工具环 reasoning_content 契约,Anthropic thinking+tool_use 规范同款);standalone/隔断/跨 turn 不投影;无 thinking 事件链投影逐字节不变;1.22 加法扩展(1.22 迭代规划 D-B1):多块语义——一块一事件(多块链每事件 meta.blockIndex=块序,单块链 meta 与 1.21 逐字节同形),投影单块 = 1.21 三元组、多块 = blocks 数组(D-B2,消费方 toAnthropic 双形态展开);1.24-W2 加法扩展(Q-W2 / D-W2-1):system kind payload.type='settlement' 回合结算回执(actor='runtime',一 turn 一张——closeTurn〔turnEnd 之前〕与 recoverInterrupted 补记两产出点,字段见 loop/settlement.ts,CAR_SETTLEMENT=off 可关;model_visible=0 白名单不投影同 usage 纪律));
|
|
15
15
|
export type Actor = 'user' | 'model' | 'plugin' | 'runtime'
|
|
16
16
|
export interface SessionEvent {
|
|
17
17
|
seq: number
|
|
@@ -156,6 +156,10 @@ export class SessionLog {
|
|
|
156
156
|
let pendingThinkingTurn: string | null = null
|
|
157
157
|
let pendingBlocks: Array<{ text: string; signature?: string; redacted?: boolean }> = []
|
|
158
158
|
for (const e of projected) {
|
|
159
|
+
// 1.24-W2(D-W2-1):settlement 回执不投影(model_visible=0 同 usage 纪律——结算凭据是
|
|
160
|
+
// 审计面事实非模型上下文;system kind 语义位但 payload.type 判别排除,先于 thinking
|
|
161
|
+
// 隔断检查,杜绝任何投影面影响;无 settlement 链投影逐字节不变)
|
|
162
|
+
if (e.kind === 'system' && (e.payload as { type?: unknown } | null)?.type === 'settlement') continue
|
|
159
163
|
if (pendingBlocks.length && (e.turnId !== pendingThinkingTurn || e.kind === 'user' || e.kind === 'system')) pendingBlocks = []
|
|
160
164
|
if (e.kind === 'thinking') {
|
|
161
165
|
const p = typeof e.payload === 'string' ? e.payload : ''
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"base64Signature":"MEQCIG5kpHlRBiuR0C1XTzOWawwa6B3jaeuUCD30RgQpZCPdAiBO9uojSbYcCjrLKnD668Am0dOc0yjRdG7lTZ92TpEwWQ==","cert":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUc1akNDQm15Z0F3SUJBZ0lVRkdNdTBsKytWSE1uK0lQbFNTVnNPSEkrbDRJd0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpZeE1URTBNVEF3T1RFeVdoY05Nall4TVRFME1UQXhPVEV5V2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVjcW03aXVPL1ljT21HcTdVYXpCZUtSa0JVQlpPbXZYREdLaXcKQ2J3TFJhMHdKS1lUQW1QQWRwMDRFR2s3ZXB1WDVBeGdIaG03b1pLSFRKK0ZWd0tBY0tPQ0JZc3dnZ1dITUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWZCZ05WSFNNRUdEQVdnQlRmCjArblBWaVFSbHZtbzJPa29WYUxHTGhoa1B6QmlCZ05WSFJFQkFmOEVXREJXaGxSb2RIUndjem92TDJkcGRHaDEKWWk1amIyMHZiSEZqTmpJMEwyTmhjaTF5ZFc1MGFXMWxMeTVuYVhSb2RXSXZkMjl5YTJac2IzZHpMM0psYkdWaApjMlV1ZVcxc1FISmxabk12ZEdGbmN5OHhMakl5TGpFd09RWUtLd1lCQkFHRHZ6QUJBUVFyYUhSMGNITTZMeTkwCmIydGxiaTVoWTNScGIyNXpMbWRwZEdoMVluVnpaWEpqYjI1MFpXNTBMbU52YlRBVkJnb3JCZ0VFQVlPL01BRUMKQkFkeVpXeGxZWE5sTURZR0Npc0dBUVFCZzc4d0FRTUVLREF5TjJOa1pUVTRaVFZoWlRZMll6WmlaV1kwWXpVNApPV0l5TUdZeVkyUXhaRFppTVRFMk5EZ3dGUVlLS3dZQkJBR0R2ekFCQkFRSGNtVnNaV0Z6WlRBZ0Jnb3JCZ0VFCkFZTy9NQUVGQkJKc2NXTTJNalF2WTJGeUxYSjFiblJwYldVd0hnWUtLd1lCQkFHRHZ6QUJCZ1FRY21WbWN5OTAKWVdkekx6RXVNakl1TVRBN0Jnb3JCZ0VFQVlPL01BRUlCQzBNSzJoMGRIQnpPaTh2ZEc5clpXNHVZV04wYVc5dQpjeTVuYVhSb2RXSjFjMlZ5WTI5dWRHVnVkQzVqYjIwd1pBWUtLd1lCQkFHRHZ6QUJDUVJXREZSb2RIUndjem92CkwyZHBkR2gxWWk1amIyMHZiSEZqTmpJMEwyTmhjaTF5ZFc1MGFXMWxMeTVuYVhSb2RXSXZkMjl5YTJac2IzZHoKTDNKbGJHVmhjMlV1ZVcxc1FISmxabk12ZEdGbmN5OHhMakl5TGpFd09BWUtLd1lCQkFHRHZ6QUJDZ1FxRENndwpNamRqWkdVMU9HVTFZV1UyTm1NMlltVm1OR00xT0RsaU1qQm1NbU5rTVdRMllqRXhOalE0TUIwR0Npc0dBUVFCCmc3OHdBUXNFRHd3TloybDBhSFZpTFdodmMzUmxaREExQmdvckJnRUVBWU8vTUFFTUJDY01KV2gwZEhCek9pOHYKWjJsMGFIVmlMbU52YlM5c2NXTTJNalF2WTJGeUxYSjFiblJwYldVd09BWUtLd1lCQkFHRHZ6QUJEUVFxRENndwpNamRqWkdVMU9HVTFZV1UyTm1NMlltVm1OR00xT0RsaU1qQm1NbU5rTVdRMllqRXhOalE0TUNBR0Npc0dBUVFCCmc3OHdBUTRFRWd3UWNtVm1jeTkwWVdkekx6RXVNakl1TVRBYUJnb3JCZ0VFQVlPL01BRVBCQXdNQ2pFek5qRXgKT1RJM01UUXdLUVlLS3dZQkJBR0R2ekFCRUFRYkRCbG9kSFJ3Y3pvdkwyZHBkR2gxWWk1amIyMHZiSEZqTmpJMApNQmdHQ2lzR0FRUUJnNzh3QVJFRUNnd0lOamt5TXpRMU5qUXdaQVlLS3dZQkJBR0R2ekFCRWdSV0RGUm9kSFJ3CmN6b3ZMMmRwZEdoMVlpNWpiMjB2YkhGak5qSTBMMk5oY2kxeWRXNTBhVzFsTHk1bmFYUm9kV0l2ZDI5eWEyWnMKYjNkekwzSmxiR1ZoYzJVdWVXMXNRSEpsWm5NdmRHRm5jeTh4TGpJeUxqRXdPQVlLS3dZQkJBR0R2ekFCRXdRcQpEQ2d3TWpkalpHVTFPR1UxWVdVMk5tTTJZbVZtTkdNMU9EbGlNakJtTW1Oa01XUTJZakV4TmpRNE1CY0dDaXNHCkFRUUJnNzh3QVJRRUNRd0hjbVZzWldGelpUQlpCZ29yQmdFRUFZTy9NQUVWQkVzTVNXaDBkSEJ6T2k4dloybDAKYUhWaUxtTnZiUzlzY1dNMk1qUXZZMkZ5TFhKMWJuUnBiV1V2WVdOMGFXOXVjeTl5ZFc1ekx6UXpOVGd6T0RZMQpOVEl5TDJGMGRHVnRjSFJ6THpFd0ZnWUtLd1lCQkFHRHZ6QUJGZ1FJREFad2RXSnNhV013VUFZS0t3WUJCQUdECnZ6QUJHQVJDREVCeVpYQnZPbXh4WXpZeU5FQTJPVEl6TkRVMk5DOWpZWEl0Y25WdWRHbHRaVUF4TXpZeE1Ua3kKTnpFME9uSmxaanB5WldaekwzUmhaM012TVM0eU1pNHhNSUdKQmdvckJnRUVBZFo1QWdRQ0JIc0VlUUIzQUhVQQozVDB3YXNiSEVUSmpHUjRjbVdjM0FxSktYcmplUEszL2g0cHlnQzhwN280QUFBR2gyWWhTSndBQUJBTUFSakJFCkFpQmtYUkJoenJNKzlRdkpQeUQ1Qk1qNWZZNEhaQzFrdVZPOGM3SE1UejUyZHdJZ2ZqcHdUZC95YnAzTEhRaEoKd01wMUxnQXdyaFlwZFVvZDZjcUFpUDZYTWxrd0NnWUlLb1pJemowRUF3TURhQUF3WlFJd1ZsWUJyeE80d1hWNQozVERDN3F1WDJ4OTlHaFBQR3gwenQ0dUt4WVE4S2R5azhwNityL0VuSkxJSlVRc3F3THFUQWpFQWlvcGZGbWxuCmxGSXl2dnROeWV0SjRJcVBzRkpXeUZSa0xsMzAzTXRKamZNRVNwRmN0dVlrbmhmOVoyZFFVYlB0Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K","rekorBundle":{"SignedEntryTimestamp":"MEQCIEjmbV0RoSQts2UKhH/hILFK0yAWexpkjLbd/Tp/jGXHAiAdgKwePusa2Oju7+xAwaXZ+DBLjv7N/VWl6srH8VpMEQ==","Payload":{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJkMjg3MzgxNDMxMmM1ZGQ2NTQ4MGRlNDc2ZmIyY2NlYWI3YjBlYzU5YmU5MmYzN2ZiN2E3ZDZmZDRhNjFiZjgyIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJRzVrcEhsUkJpdVIwQzFYVHpPV2F3d2E2QjNqYWV1VUNEMzBSZ1FwWkNQZEFpQk85dW9qU2JZY0NqckxLbkQ2NjhBbTBkT2MweWpSZEc3bFRaOTJUcEV3V1E9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVYzFha05EUW0xNVowRjNTVUpCWjBsVlJrZE5kVEJzS3l0V1NFMXVLMGxRYkZOVFZuTlBTRWtyYkRSSmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFplRTFVUlRCTlZFRjNUMVJGZVZkb1kwNU5hbGw0VFZSRk1FMVVRWGhQVkVWNVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZqY1cwM2FYVlBMMWxqVDIxSGNUZFZZWHBDWlV0U2EwSlZRbHBQYlhaWVJFZExhWGNLUTJKM1RGSmhNSGRLUzFsVVFXMVFRV1J3TURSRlIyczNaWEIxV0RWQmVHZElhRzAzYjFwTFNGUktLMFpXZDB0QlkwdFBRMEpaYzNkbloxZElUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXWkNaMDVXU0ZOTlJVZEVRVmRuUWxSbUNqQXJibEJXYVZGU2JIWnRiekpQYTI5V1lVeEhUR2hvYTFCNlFtbENaMDVXU0ZKRlFrRm1PRVZYUkVKWGFHeFNiMlJJVW5kamVtOTJUREprY0dSSGFERUtXV2sxYW1JeU1IWmlTRVpxVG1wSk1Fd3lUbWhqYVRGNVpGYzFNR0ZYTVd4TWVUVnVZVmhTYjJSWFNYWmtNamw1WVRKYWMySXpaSHBNTTBwc1lrZFdhQXBqTWxWMVpWY3hjMUZJU214YWJrMTJaRWRHYm1ONU9IaE1ha2w1VEdwRmQwOVJXVXRMZDFsQ1FrRkhSSFo2UVVKQlVWRnlZVWhTTUdOSVRUWk1lVGt3Q21JeWRHeGlhVFZvV1ROU2NHSXlOWHBNYldSd1pFZG9NVmx1Vm5wYVdFcHFZakkxTUZwWE5UQk1iVTUyWWxSQlZrSm5iM0pDWjBWRlFWbFBMMDFCUlVNS1FrRmtlVnBYZUd4WldFNXNUVVJaUjBOcGMwZEJVVkZDWnpjNGQwRlJUVVZMUkVGNVRqSk9hMXBVVlRSYVZGWm9XbFJaTWxsNldtbGFWMWt3V1hwVk5BcFBWMGw1VFVkWmVWa3lVWGhhUkZwcFRWUkZNazVFWjNkR1VWbExTM2RaUWtKQlIwUjJla0ZDUWtGUlNHTnRWbk5hVjBaNldsUkJaMEpuYjNKQ1owVkZDa0ZaVHk5TlFVVkdRa0pLYzJOWFRUSk5hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFZkMGhuV1V0TGQxbENRa0ZIUkhaNlFVSkNaMUZSWTIxV2JXTjVPVEFLV1Zka2VreDZSWFZOYWtsMVRWUkJOMEpuYjNKQ1owVkZRVmxQTDAxQlJVbENRekJOU3pKb01HUklRbnBQYVRoMlpFYzVjbHBYTkhWWlYwNHdZVmM1ZFFwamVUVnVZVmhTYjJSWFNqRmpNbFo1V1RJNWRXUkhWblZrUXpWcVlqSXdkMXBCV1V0TGQxbENRa0ZIUkhaNlFVSkRVVkpYUkVaU2IyUklVbmRqZW05MkNrd3laSEJrUjJneFdXazFhbUl5TUhaaVNFWnFUbXBKTUV3eVRtaGphVEY1WkZjMU1HRlhNV3hNZVRWdVlWaFNiMlJYU1haa01qbDVZVEphYzJJelpIb0tURE5LYkdKSFZtaGpNbFYxWlZjeGMxRklTbXhhYmsxMlpFZEdibU41T0hoTWFrbDVUR3BGZDA5QldVdExkMWxDUWtGSFJIWjZRVUpEWjFGeFJFTm5kd3BOYW1ScVdrZFZNVTlIVlRGWlYxVXlUbTFOTWxsdFZtMU9SMDB4VDBSc2FVMXFRbTFOYlU1clRWZFJNbGxxUlhoT2FsRTBUVUl3UjBOcGMwZEJVVkZDQ21jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacFRGZG9kbU16VW14YVJFRXhRbWR2Y2tKblJVVkJXVTh2VFVGRlRVSkRZMDFLVjJnd1pFaENlazlwT0hZS1dqSnNNR0ZJVm1sTWJVNTJZbE01YzJOWFRUSk5hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFZkMDlCV1V0TGQxbENRa0ZIUkhaNlFVSkVVVkZ4UkVObmR3cE5hbVJxV2tkVk1VOUhWVEZaVjFVeVRtMU5NbGx0Vm0xT1IwMHhUMFJzYVUxcVFtMU5iVTVyVFZkUk1sbHFSWGhPYWxFMFRVTkJSME5wYzBkQlVWRkNDbWMzT0hkQlVUUkZSV2QzVVdOdFZtMWplVGt3V1Zka2VreDZSWFZOYWtsMVRWUkJZVUpuYjNKQ1owVkZRVmxQTDAxQlJWQkNRWGROUTJwRmVrNXFSWGdLVDFSSk0wMVVVWGRMVVZsTFMzZFpRa0pCUjBSMmVrRkNSVUZSWWtSQ2JHOWtTRkozWTNwdmRrd3laSEJrUjJneFdXazFhbUl5TUhaaVNFWnFUbXBKTUFwTlFtZEhRMmx6UjBGUlVVSm5OemgzUVZKRlJVTm5kMGxPYW10NVRYcFJNVTVxVVhkYVFWbExTM2RaUWtKQlIwUjJla0ZDUldkU1YwUkdVbTlrU0ZKM0NtTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJZa2hHYWs1cVNUQk1NazVvWTJreGVXUlhOVEJoVnpGc1RIazFibUZZVW05a1YwbDJaREk1ZVdFeVduTUtZak5rZWt3elNteGlSMVpvWXpKVmRXVlhNWE5SU0Vwc1dtNU5kbVJIUm01amVUaDRUR3BKZVV4cVJYZFBRVmxMUzNkWlFrSkJSMFIyZWtGQ1JYZFJjUXBFUTJkM1RXcGthbHBIVlRGUFIxVXhXVmRWTWs1dFRUSlpiVlp0VGtkTk1VOUViR2xOYWtKdFRXMU9hMDFYVVRKWmFrVjRUbXBSTkUxQ1kwZERhWE5IQ2tGUlVVSm5OemgzUVZKUlJVTlJkMGhqYlZaeldsZEdlbHBVUWxwQ1oyOXlRbWRGUlVGWlR5OU5RVVZXUWtWelRWTlhhREJrU0VKNlQyazRkbG95YkRBS1lVaFdhVXh0VG5aaVV6bHpZMWROTWsxcVVYWlpNa1o1VEZoS01XSnVVbkJpVjFWMldWZE9NR0ZYT1hWamVUbDVaRmMxZWt4NlVYcE9WR2Q2VDBSWk1RcE9WRWw1VERKR01HUkhWblJqU0ZKNlRIcEZkMFpuV1V0TGQxbENRa0ZIUkhaNlFVSkdaMUZKUkVGYWQyUlhTbk5oVjAxM1ZVRlpTMHQzV1VKQ1FVZEVDblo2UVVKSFFWSkRSRVZDZVZwWVFuWlBiWGg0V1hwWmVVNUZRVEpQVkVsNlRrUlZNazVET1dwWldFbDBZMjVXZFdSSGJIUmFWVUY0VFhwWmVFMVVhM2tLVG5wRk1FOXVTbXhhYW5CNVdsZGFla3d6VW1oYU0wMTJUVk0wZVUxcE5IaE5TVWRLUW1kdmNrSm5SVVZCWkZvMVFXZFJRMEpJYzBWbFVVSXpRVWhWUVFvelZEQjNZWE5pU0VWVVNtcEhValJqYlZkak0wRnhTa3RZY21wbFVFc3pMMmcwY0hsblF6aHdOMjgwUVVGQlIyZ3lXV2hUU25kQlFVSkJUVUZTYWtKRkNrRnBRbXRZVWtKb2VuSk5LemxSZGtwUWVVUTFRazFxTldaWk5FaGFRekZyZFZaUE9HTTNTRTFVZWpVeVpIZEpaMlpxY0hkVVpDOTVZbkF6VEVoUmFFb0tkMDF3TVV4blFYZHlhRmx3WkZWdlpEWmpjVUZwVURaWVRXeHJkME5uV1VsTGIxcEplbW93UlVGM1RVUmhRVUYzV2xGSmQxWnNXVUp5ZUU4MGQxaFdOUW96VkVSRE4zRjFXREo0T1RsSGFGQlFSM2d3ZW5RMGRVdDRXVkU0UzJSNWF6aHdOaXR5TDBWdVNreEpTbFZSYzNGM1RIRlVRV3BGUVdsdmNHWkdiV3h1Q214R1NYbDJkblJPZVdWMFNqUkpjVkJ6UmtwWGVVWlNhMHhzTXpBelRYUkthbVpOUlZOd1JtTjBkVmxyYm1obU9Wb3laRkZWWWxCMENpMHRMUzB0UlU1RUlFTkZVbFJKUmtsRFFWUkZMUzB0TFMwSyJ9fX19","integratedTime":1794650953,"logIndex":3875442005,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}}
|