@lqc123qwe/car-runtime 1.22.1 → 1.23.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 CHANGED
@@ -1 +1 @@
1
- d2873814312c5dd65480de476fb2cceab7b0ec59be92f37fb7a7d6fd4a61bf82 car-runtime-1.22.1.tgz
1
+ 6e62dc16fe612b7bb524a8233c60428b1a6f76685dd9d9da75dc7749eadd0cf0 car-runtime-1.23.0.tgz
Binary file
@@ -0,0 +1 @@
1
+ {"base64Signature":"MEYCIQDW2ZdCjKblGFqrvmJOVOwajIkbcz3Y5rSVCO/xoU0CgwIhALOUpcq1yOaJXrvFJPepFRLaUqz1gAcNINk5Ix81rKrv","cert":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUc2VENDQm02Z0F3SUJBZ0lVVUhrY0dRTEVYcnFOWFdNSzN0TnQxaE01R3ljd0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpZeE1URTFNRGN5TXpBeVdoY05Nall4TVRFMU1EY3pNekF5V2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVIWU1COWxHSExQelVFd291cFB1S2s5OExENnpsZzlyUURaY3oKR2g2YzhZY0N0a0VUVnZnN0g2eU5UL3VudjA3VG0raEdSVVNoUHBkVTFxekNCT1A3NWFPQ0JZMHdnZ1dKTUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWZCZ05WSFNNRUdEQVdnQlRmCjArblBWaVFSbHZtbzJPa29WYUxHTGhoa1B6QmlCZ05WSFJFQkFmOEVXREJXaGxSb2RIUndjem92TDJkcGRHaDEKWWk1amIyMHZiSEZqTmpJMEwyTmhjaTF5ZFc1MGFXMWxMeTVuYVhSb2RXSXZkMjl5YTJac2IzZHpMM0psYkdWaApjMlV1ZVcxc1FISmxabk12ZEdGbmN5OHhMakl6TGpBd09RWUtLd1lCQkFHRHZ6QUJBUVFyYUhSMGNITTZMeTkwCmIydGxiaTVoWTNScGIyNXpMbWRwZEdoMVluVnpaWEpqYjI1MFpXNTBMbU52YlRBVkJnb3JCZ0VFQVlPL01BRUMKQkFkeVpXeGxZWE5sTURZR0Npc0dBUVFCZzc4d0FRTUVLRFUwTURWa1lqbGlaR0U0TURaak0yTXpaV1ZrWVRVMwpOV014WldJMFlXVTBPREE0WWpWaE16SXdGUVlLS3dZQkJBR0R2ekFCQkFRSGNtVnNaV0Z6WlRBZ0Jnb3JCZ0VFCkFZTy9NQUVGQkJKc2NXTTJNalF2WTJGeUxYSjFiblJwYldVd0hnWUtLd1lCQkFHRHZ6QUJCZ1FRY21WbWN5OTAKWVdkekx6RXVNak11TURBN0Jnb3JCZ0VFQVlPL01BRUlCQzBNSzJoMGRIQnpPaTh2ZEc5clpXNHVZV04wYVc5dQpjeTVuYVhSb2RXSjFjMlZ5WTI5dWRHVnVkQzVqYjIwd1pBWUtLd1lCQkFHRHZ6QUJDUVJXREZSb2RIUndjem92CkwyZHBkR2gxWWk1amIyMHZiSEZqTmpJMEwyTmhjaTF5ZFc1MGFXMWxMeTVuYVhSb2RXSXZkMjl5YTJac2IzZHoKTDNKbGJHVmhjMlV1ZVcxc1FISmxabk12ZEdGbmN5OHhMakl6TGpBd09BWUtLd1lCQkFHRHZ6QUJDZ1FxRENnMQpOREExWkdJNVltUmhPREEyWXpOak0yVmxaR0UxTnpWak1XVmlOR0ZsTkRnd09HSTFZVE15TUIwR0Npc0dBUVFCCmc3OHdBUXNFRHd3TloybDBhSFZpTFdodmMzUmxaREExQmdvckJnRUVBWU8vTUFFTUJDY01KV2gwZEhCek9pOHYKWjJsMGFIVmlMbU52YlM5c2NXTTJNalF2WTJGeUxYSjFiblJwYldVd09BWUtLd1lCQkFHRHZ6QUJEUVFxRENnMQpOREExWkdJNVltUmhPREEyWXpOak0yVmxaR0UxTnpWak1XVmlOR0ZsTkRnd09HSTFZVE15TUNBR0Npc0dBUVFCCmc3OHdBUTRFRWd3UWNtVm1jeTkwWVdkekx6RXVNak11TURBYUJnb3JCZ0VFQVlPL01BRVBCQXdNQ2pFek5qRXgKT1RJM01UUXdLUVlLS3dZQkJBR0R2ekFCRUFRYkRCbG9kSFJ3Y3pvdkwyZHBkR2gxWWk1amIyMHZiSEZqTmpJMApNQmdHQ2lzR0FRUUJnNzh3QVJFRUNnd0lOamt5TXpRMU5qUXdaQVlLS3dZQkJBR0R2ekFCRWdSV0RGUm9kSFJ3CmN6b3ZMMmRwZEdoMVlpNWpiMjB2YkhGak5qSTBMMk5oY2kxeWRXNTBhVzFsTHk1bmFYUm9kV0l2ZDI5eWEyWnMKYjNkekwzSmxiR1ZoYzJVdWVXMXNRSEpsWm5NdmRHRm5jeTh4TGpJekxqQXdPQVlLS3dZQkJBR0R2ekFCRXdRcQpEQ2cxTkRBMVpHSTVZbVJoT0RBMll6TmpNMlZsWkdFMU56VmpNV1ZpTkdGbE5EZ3dPR0kxWVRNeU1CY0dDaXNHCkFRUUJnNzh3QVJRRUNRd0hjbVZzWldGelpUQlpCZ29yQmdFRUFZTy9NQUVWQkVzTVNXaDBkSEJ6T2k4dloybDAKYUhWaUxtTnZiUzlzY1dNMk1qUXZZMkZ5TFhKMWJuUnBiV1V2WVdOMGFXOXVjeTl5ZFc1ekx6UXpOamc1TmpnNApNekV3TDJGMGRHVnRjSFJ6THpFd0ZnWUtLd1lCQkFHRHZ6QUJGZ1FJREFad2RXSnNhV013VUFZS0t3WUJCQUdECnZ6QUJHQVJDREVCeVpYQnZPbXh4WXpZeU5FQTJPVEl6TkRVMk5DOWpZWEl0Y25WdWRHbHRaVUF4TXpZeE1Ua3kKTnpFME9uSmxaanB5WldaekwzUmhaM012TVM0eU15NHdNSUdMQmdvckJnRUVBZFo1QWdRQ0JIMEVld0I1QUhjQQozVDB3YXNiSEVUSmpHUjRjbVdjM0FxSktYcmplUEszL2g0cHlnQzhwN280QUFBR2gzaGFQbmdBQUJBTUFTREJHCkFpRUF2Wkp2ZFdYdHZXc2VxOWR5MGRjWU43L0F3Q1BJZ0dRV3FMME1oTWZZb0xnQ0lRRDdYTk1sVnZ4VGE2ZUQKZVZ3ZEczcGhjZWh4NVhrTTZqVTdyRkY4VmpTdyt6QUtCZ2dxaGtqT1BRUURBd05wQURCbUFqRUFnaUptNlBGQwprWjV5bHNDaW42a0pFQkFHVjZyRW1QV0xxQW5yak5seDRUVnRZSnBHMm5UQThJNFBRRkFrV0ROdUFqRUF6dlJqCmg3dEhTNVQzYWlBVXZvVmlMZmZnSlgzMk52dk5RdFJCWndZQlBya3F2RW40OU0weFBoa0VqQWZ5TVdiQwotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==","rekorBundle":{"SignedEntryTimestamp":"MEQCIDFX1LK6tAMq5Zen3TbpVZE22INBCyh2O1gxEqUxIE8MAiA6L4cok518P7smZs+PnaPqiaXNB3zTFGx8g3v3hJW4/g==","Payload":{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI2ZTYyZGMxNmZlNjEyYjdiYjUyNGE4MjMzYzYwNDI4YjFhNmY3NjY4NWRkOWQ5ZGE3NWRjNzc0OWVhZGQwY2YwIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FWUNJUURXMlpkQ2pLYmxHRnFydm1KT1ZPd2FqSWtiY3ozWTVyU1ZDTy94b1UwQ2d3SWhBTE9VcGNxMXlPYUpYcnZGSlBlcEZSTGFVcXoxZ0FjTklOazVJeDgxcktydiIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVYzJWRU5EUW0wMlowRjNTVUpCWjBsVlZVaHJZMGRSVEVWWWNuRk9XRmROU3pOMFRuUXhhRTAxUjNsamQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFplRTFVUlRGTlJHTjVUWHBCZVZkb1kwNU5hbGw0VFZSRk1VMUVZM3BOZWtGNVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZJV1UxQ09XeEhTRXhRZWxWRmQyOTFjRkIxUzJzNU9FeEVObnBzWnpseVVVUmFZM29LUjJnMll6aFpZME4wYTBWVVZuWm5OMGcyZVU1VUwzVnVkakEzVkcwcmFFZFNWVk5vVUhCa1ZURnhla05DVDFBM05XRlBRMEpaTUhkbloxZEtUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXWkNaMDVXU0ZOTlJVZEVRVmRuUWxSbUNqQXJibEJXYVZGU2JIWnRiekpQYTI5V1lVeEhUR2hvYTFCNlFtbENaMDVXU0ZKRlFrRm1PRVZYUkVKWGFHeFNiMlJJVW5kamVtOTJUREprY0dSSGFERUtXV2sxYW1JeU1IWmlTRVpxVG1wSk1Fd3lUbWhqYVRGNVpGYzFNR0ZYTVd4TWVUVnVZVmhTYjJSWFNYWmtNamw1WVRKYWMySXpaSHBNTTBwc1lrZFdhQXBqTWxWMVpWY3hjMUZJU214YWJrMTJaRWRHYm1ONU9IaE1ha2w2VEdwQmQwOVJXVXRMZDFsQ1FrRkhSSFo2UVVKQlVWRnlZVWhTTUdOSVRUWk1lVGt3Q21JeWRHeGlhVFZvV1ROU2NHSXlOWHBNYldSd1pFZG9NVmx1Vm5wYVdFcHFZakkxTUZwWE5UQk1iVTUyWWxSQlZrSm5iM0pDWjBWRlFWbFBMMDFCUlVNS1FrRmtlVnBYZUd4WldFNXNUVVJaUjBOcGMwZEJVVkZDWnpjNGQwRlJUVVZMUkZVd1RVUldhMWxxYkdsYVIwVTBUVVJhYWsweVRYcGFWMVpyV1ZSVk13cE9WMDE0V2xkSk1GbFhWVEJQUkVFMFdXcFdhRTE2U1hkR1VWbExTM2RaUWtKQlIwUjJla0ZDUWtGUlNHTnRWbk5hVjBaNldsUkJaMEpuYjNKQ1owVkZDa0ZaVHk5TlFVVkdRa0pLYzJOWFRUSk5hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFZkMGhuV1V0TGQxbENRa0ZIUkhaNlFVSkNaMUZSWTIxV2JXTjVPVEFLV1Zka2VreDZSWFZOYWsxMVRVUkJOMEpuYjNKQ1owVkZRVmxQTDAxQlJVbENRekJOU3pKb01HUklRbnBQYVRoMlpFYzVjbHBYTkhWWlYwNHdZVmM1ZFFwamVUVnVZVmhTYjJSWFNqRmpNbFo1V1RJNWRXUkhWblZrUXpWcVlqSXdkMXBCV1V0TGQxbENRa0ZIUkhaNlFVSkRVVkpYUkVaU2IyUklVbmRqZW05MkNrd3laSEJrUjJneFdXazFhbUl5TUhaaVNFWnFUbXBKTUV3eVRtaGphVEY1WkZjMU1HRlhNV3hNZVRWdVlWaFNiMlJYU1haa01qbDVZVEphYzJJelpIb0tURE5LYkdKSFZtaGpNbFYxWlZjeGMxRklTbXhhYmsxMlpFZEdibU41T0hoTWFrbDZUR3BCZDA5QldVdExkMWxDUWtGSFJIWjZRVUpEWjFGeFJFTm5NUXBPUkVFeFdrZEpOVmx0VW1oUFJFRXlXWHBPYWsweVZteGFSMFV4VG5wV2FrMVhWbWxPUjBac1RrUm5kMDlIU1RGWlZFMTVUVUl3UjBOcGMwZEJVVkZDQ21jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacFRGZG9kbU16VW14YVJFRXhRbWR2Y2tKblJVVkJXVTh2VFVGRlRVSkRZMDFLVjJnd1pFaENlazlwT0hZS1dqSnNNR0ZJVm1sTWJVNTJZbE01YzJOWFRUSk5hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFZkMDlCV1V0TGQxbENRa0ZIUkhaNlFVSkVVVkZ4UkVObk1RcE9SRUV4V2tkSk5WbHRVbWhQUkVFeVdYcE9hazB5Vm14YVIwVXhUbnBXYWsxWFZtbE9SMFpzVGtSbmQwOUhTVEZaVkUxNVRVTkJSME5wYzBkQlVWRkNDbWMzT0hkQlVUUkZSV2QzVVdOdFZtMWplVGt3V1Zka2VreDZSWFZOYWsxMVRVUkJZVUpuYjNKQ1owVkZRVmxQTDAxQlJWQkNRWGROUTJwRmVrNXFSWGdLVDFSSk0wMVVVWGRMVVZsTFMzZFpRa0pCUjBSMmVrRkNSVUZSWWtSQ2JHOWtTRkozWTNwdmRrd3laSEJrUjJneFdXazFhbUl5TUhaaVNFWnFUbXBKTUFwTlFtZEhRMmx6UjBGUlVVSm5OemgzUVZKRlJVTm5kMGxPYW10NVRYcFJNVTVxVVhkYVFWbExTM2RaUWtKQlIwUjJla0ZDUldkU1YwUkdVbTlrU0ZKM0NtTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJZa2hHYWs1cVNUQk1NazVvWTJreGVXUlhOVEJoVnpGc1RIazFibUZZVW05a1YwbDJaREk1ZVdFeVduTUtZak5rZWt3elNteGlSMVpvWXpKVmRXVlhNWE5SU0Vwc1dtNU5kbVJIUm01amVUaDRUR3BKZWt4cVFYZFBRVmxMUzNkWlFrSkJSMFIyZWtGQ1JYZFJjUXBFUTJjeFRrUkJNVnBIU1RWWmJWSm9UMFJCTWxsNlRtcE5NbFpzV2tkRk1VNTZWbXBOVjFacFRrZEdiRTVFWjNkUFIwa3hXVlJOZVUxQ1kwZERhWE5IQ2tGUlVVSm5OemgzUVZKUlJVTlJkMGhqYlZaeldsZEdlbHBVUWxwQ1oyOXlRbWRGUlVGWlR5OU5RVVZXUWtWelRWTlhhREJrU0VKNlQyazRkbG95YkRBS1lVaFdhVXh0VG5aaVV6bHpZMWROTWsxcVVYWlpNa1o1VEZoS01XSnVVbkJpVjFWMldWZE9NR0ZYT1hWamVUbDVaRmMxZWt4NlVYcE9hbWMxVG1wbk5BcE5la1YzVERKR01HUkhWblJqU0ZKNlRIcEZkMFpuV1V0TGQxbENRa0ZIUkhaNlFVSkdaMUZKUkVGYWQyUlhTbk5oVjAxM1ZVRlpTMHQzV1VKQ1FVZEVDblo2UVVKSFFWSkRSRVZDZVZwWVFuWlBiWGg0V1hwWmVVNUZRVEpQVkVsNlRrUlZNazVET1dwWldFbDBZMjVXZFdSSGJIUmFWVUY0VFhwWmVFMVVhM2tLVG5wRk1FOXVTbXhhYW5CNVdsZGFla3d6VW1oYU0wMTJUVk0wZVUxNU5IZE5TVWRNUW1kdmNrSm5SVVZCWkZvMVFXZFJRMEpJTUVWbGQwSTFRVWhqUVFvelZEQjNZWE5pU0VWVVNtcEhValJqYlZkak0wRnhTa3RZY21wbFVFc3pMMmcwY0hsblF6aHdOMjgwUVVGQlIyZ3phR0ZRYm1kQlFVSkJUVUZUUkVKSENrRnBSVUYyV2twMlpGZFlkSFpYYzJWeE9XUjVNR1JqV1U0M0wwRjNRMUJKWjBkUlYzRk1NRTFvVFdaWmIweG5RMGxSUkRkWVRrMXNWblo0VkdFMlpVUUtaVlozWkVjemNHaGpaV2g0TlZoclRUWnFWVGR5UmtZNFZtcFRkeXQ2UVV0Q1oyZHhhR3RxVDFCUlVVUkJkMDV3UVVSQ2JVRnFSVUZuYVVwdE5sQkdRd3ByV2pWNWJITkRhVzQyYTBwRlFrRkhWalp5UlcxUVYweHhRVzV5YWs1c2VEUlVWblJaU25CSE1tNVVRVGhKTkZCUlJrRnJWMFJPZFVGcVJVRjZkbEpxQ21nM2RFaFROVlF6WVdsQlZYWnZWbWxNWm1ablNsZ3pNazUyZGs1UmRGSkNXbmRaUWxCeWEzRjJSVzQwT1Uwd2VGQm9hMFZxUVdaNVRWZGlRd290TFMwdExVVk9SQ0JEUlZKVVNVWkpRMEZVUlMwdExTMHRDZz09In19fX0=","integratedTime":1794727383,"logIndex":3896882215,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}}
@@ -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
- constructor();
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,16 @@
1
1
  import { CarM8Error } from '../runtime-core/errors.js';
2
+ /**
3
+ * 1.23-W1(D-W1-6):取消时机标注(W-0 审计 F-W0-3——aborted 三路径链上不区分)——
4
+ * 'before-request' = 请求未发出(runTurn 循环头 / chatStep 零 chunk 流结束检查);
5
+ * 'in-flight' = 在途打断(chatStep chunk 间隙 / 有 chunk 后的流结束检查)。
6
+ * 缺省缺席 = 1.22 形态(terminate 型 abort 为工具主动收口语义,非取消,不带键)。
7
+ */
2
8
  export class TurnAborted extends Error {
3
- constructor() { super('CAR-ABORTED: turn cancelled by user'); }
9
+ abortedAt;
10
+ constructor(abortedAt) {
11
+ super('CAR-ABORTED: turn cancelled by user');
12
+ this.abortedAt = abortedAt;
13
+ }
4
14
  }
5
15
  /**
6
16
  * 1.11-S2(D-33 / 计量设计 §3.3):turn 级 usage 聚合——扫描本 turn 的 'usage' 事件(事实本体
@@ -166,7 +176,8 @@ export async function runTurn(opts) {
166
176
  try {
167
177
  while (steps < maxSteps) {
168
178
  if (signal?.aborted) {
169
- const seq = closeTurn({ reason: 'aborted' }).seq;
179
+ // 1.23-W1(D-W1-6):循环头取消 = 请求未发出(无在途请求)
180
+ const seq = closeTurn({ reason: 'aborted', abortedAt: 'before-request' }).seq;
170
181
  trail.push({ seq, reason: 'aborted' });
171
182
  return { reason: 'aborted', steps, endReasonTrail: trail };
172
183
  }
@@ -252,17 +263,19 @@ export async function runTurn(opts) {
252
263
  }
253
264
  catch (e) {
254
265
  if (e instanceof TurnAborted) {
255
- const seq = closeTurn({ reason: 'aborted' }).seq;
266
+ // 1.23-W1(D-W1-6):chatStep 甄别过的取消时机随异常携带(terminate 型 abort 不经此路径)
267
+ const seq = closeTurn({ reason: 'aborted', ...(e.abortedAt ? { abortedAt: e.abortedAt } : {}) }).seq;
256
268
  trail.push({ seq, reason: 'aborted' });
257
269
  return { reason: 'aborted', steps, endReasonTrail: trail };
258
270
  }
259
271
  // 1.4-S4:CarM8Error 保形——code/userHint/retryable 进 turnEnd(A080001 引导文案「请运行
260
- // car doctor」可达用户;非 CarM8Error 维持 String(e) 口径不变)
272
+ // car doctor」可达用户;非 CarM8Error 维持 String(e) 口径不变);
273
+ // 1.23-W1(D-W1-3):+kind 失败细类(加法尾键——B080001 多形态分型,审计 F-W0-2)
261
274
  const m8 = e instanceof CarM8Error;
262
275
  const detail = m8 ? `${e.slug}: ${e.message}` : String(e);
263
276
  const seq = closeTurn({
264
277
  reason: 'error', detail,
265
- ...(m8 ? { code: e.code, userHint: e.userHint, retryable: e.retryable } : {}),
278
+ ...(m8 ? { code: e.code, userHint: e.userHint, retryable: e.retryable, ...(e.kind ? { kind: e.kind } : {}) } : {}),
266
279
  }).seq;
267
280
  trail.push({ seq, reason: 'error' });
268
281
  return { reason: 'error', steps, endReasonTrail: trail };
@@ -1,4 +1,4 @@
1
- {"stage":"archive","detail":"git archive HEAD → car-runtime-1.22.1.tgz(444943 bytes,树哈希锚定)","ts":1794650951102}
2
- {"stage":"sha256sums","detail":"SHA256SUMS d2873814312c5dd6…(裸字节复核通过)","ts":1794650951103}
3
- {"stage":"sbom","detail":"55 个组件(运行时依赖 0 项)","ts":1794650951107}
4
- {"stage":"cosign-keyless","detail":"signed car-runtime-1.22.1.tgz (keyless OIDC + Rekor)","ts":1794650954633}
1
+ {"stage":"archive","detail":"git archive HEAD → car-runtime-1.23.0.tgz(450442 bytes,树哈希锚定)","ts":1794727381948}
2
+ {"stage":"sha256sums","detail":"SHA256SUMS 6e62dc16fe612b7b…(裸字节复核通过)","ts":1794727381950}
3
+ {"stage":"sbom","detail":"55 个组件(运行时依赖 0 项)","ts":1794727381957}
4
+ {"stage":"cosign-keyless","detail":"signed car-runtime-1.23.0.tgz (keyless OIDC + Rekor)","ts":1794727387719}
@@ -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
- // 1.4-S4(D-19):chunk 间隙取消——此刻 assistant 尚未落链(流完才 append),无半截消息
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
- throw new Error(`CAR-E-LLM-PROTO: 工具调用 ${c.name} 参数非法 JSON(finishReason=${stopReason},${errorDetail ?? '无附加错误'})`);
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
  }
@@ -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
- yield { finishReason: 'error', error: { code: 'B080001', message: `provider HTTP ${res.status}${text ? `:${text.slice(0, 200)}` : ''}` } };
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
- yield { finishReason: 'error', error: { code: 'B080001', message: '流结束未携带 finish_reason(AL-05)' } };
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, ...(finish === 'error' ? { error: { code: 'B080001', message: `未映射的 finish_reason "${raw}"` } } : {}) }
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, ...(finish === 'error' ? { error: { code: 'B080001', message: `未映射的 finish_reason "${raw}"` } } : {}) }
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
- ? { finishReason: finish }
805
- : { finishReason: 'error', error: { code: 'B080001', message: `未映射的 stop_reason "${raw}"` } };
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
- yield { finishReason: 'error', error: { code: 'B080001', message: `provider 流错误:${evt.error?.message ?? JSON.stringify(evt).slice(0, 200)}` } };
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.22.1",
4
+ "version": "1.23.0",
5
5
  "components": [
6
6
  {
7
7
  "type": "file",
@@ -106,7 +106,7 @@
106
106
  {
107
107
  "type": "file",
108
108
  "path": "src/loop/stop.ts",
109
- "sha256": "4b144151b40c07445592795006fd9b3388cb2e9bc398dd41c5d3df883853e098"
109
+ "sha256": "a39751b233cbf42073af08fd9a6ba29f29a47700cd025fa79a3d5418e1116a60"
110
110
  },
111
111
  {
112
112
  "type": "file",
@@ -161,7 +161,7 @@
161
161
  {
162
162
  "type": "file",
163
163
  "path": "src/runtime-core/chatStep.ts",
164
- "sha256": "8847c49264abcb451bb9587c1c15abb06c940038be8f95ddcf8a8ba776b58d03"
164
+ "sha256": "c95f71f3fbe5a99b2fe02d3a71450efb2bb31b003a30da6eafc39eb57c791f33"
165
165
  },
166
166
  {
167
167
  "type": "file",
@@ -171,12 +171,12 @@
171
171
  {
172
172
  "type": "file",
173
173
  "path": "src/runtime-core/errors.ts",
174
- "sha256": "e94d3ce47aba5ed85fffcc468bc785e8d083b689385c91321f397a5cbb3a445b"
174
+ "sha256": "b7611e5a1647bb0022a76b9f8211a59f8097c0d70c4747d56600e122ca5198a8"
175
175
  },
176
176
  {
177
177
  "type": "file",
178
178
  "path": "src/runtime-core/llm.ts",
179
- "sha256": "8e8e255b0de192c6118d5972a62e30a4812ce72f8d3acd5641f8241b63c842f6"
179
+ "sha256": "fcab344c1dae5a4fac24dcb8e7be3a82cf0442411b4b9943e558a07f35e86a30"
180
180
  },
181
181
  {
182
182
  "type": "file",
@@ -201,7 +201,7 @@
201
201
  {
202
202
  "type": "file",
203
203
  "path": "src/runtime-core/types.ts",
204
- "sha256": "fbe2d03167420169ebe601bd425e641f3df5ad518d109995ff5302b839a82480"
204
+ "sha256": "c11f7fbb38ff1f5567e7a18c87300f3bc3bf802b7d3660ebb30a8191017ad37a"
205
205
  },
206
206
  {
207
207
  "type": "file",
@@ -271,7 +271,7 @@
271
271
  {
272
272
  "type": "file",
273
273
  "path": "package.json",
274
- "sha256": "105c8c6caf3b46768fac8ed106f305b85307a71e5bdf55cc9395212584c4161a"
274
+ "sha256": "593002604fb264718099c16d5a3d04b82d1a1073b7c8f8160fc7835f4a695e50"
275
275
  },
276
276
  {
277
277
  "type": "file",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lqc123qwe/car-runtime",
3
- "version": "1.22.1",
3
+ "version": "1.23.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/stop.ts CHANGED
@@ -74,8 +74,14 @@ export interface TurnPreset {
74
74
 
75
75
  export interface TurnResult { reason: TurnEndReason; steps: number; endReasonTrail: Array<{ seq: number; reason: TurnEndReason }> }
76
76
 
77
+ /**
78
+ * 1.23-W1(D-W1-6):取消时机标注(W-0 审计 F-W0-3——aborted 三路径链上不区分)——
79
+ * 'before-request' = 请求未发出(runTurn 循环头 / chatStep 零 chunk 流结束检查);
80
+ * 'in-flight' = 在途打断(chatStep chunk 间隙 / 有 chunk 后的流结束检查)。
81
+ * 缺省缺席 = 1.22 形态(terminate 型 abort 为工具主动收口语义,非取消,不带键)。
82
+ */
77
83
  export class TurnAborted extends Error {
78
- constructor() { super('CAR-ABORTED: turn cancelled by user') }
84
+ constructor(readonly abortedAt?: 'before-request' | 'in-flight') { super('CAR-ABORTED: turn cancelled by user') }
79
85
  }
80
86
 
81
87
  /**
@@ -231,7 +237,8 @@ export async function runTurn(opts: {
231
237
  try {
232
238
  while (steps < maxSteps) {
233
239
  if (signal?.aborted) {
234
- const seq = closeTurn({ reason: 'aborted' }).seq
240
+ // 1.23-W1(D-W1-6):循环头取消 = 请求未发出(无在途请求)
241
+ const seq = closeTurn({ reason: 'aborted', abortedAt: 'before-request' }).seq
235
242
  trail.push({ seq, reason: 'aborted' })
236
243
  return { reason: 'aborted', steps, endReasonTrail: trail }
237
244
  }
@@ -320,17 +327,19 @@ export async function runTurn(opts: {
320
327
  return { reason: 'error', steps, endReasonTrail: trail }
321
328
  } catch (e) {
322
329
  if (e instanceof TurnAborted) {
323
- const seq = closeTurn({ reason: 'aborted' }).seq
330
+ // 1.23-W1(D-W1-6):chatStep 甄别过的取消时机随异常携带(terminate 型 abort 不经此路径)
331
+ const seq = closeTurn({ reason: 'aborted', ...(e.abortedAt ? { abortedAt: e.abortedAt } : {}) }).seq
324
332
  trail.push({ seq, reason: 'aborted' })
325
333
  return { reason: 'aborted', steps, endReasonTrail: trail }
326
334
  }
327
335
  // 1.4-S4:CarM8Error 保形——code/userHint/retryable 进 turnEnd(A080001 引导文案「请运行
328
- // car doctor」可达用户;非 CarM8Error 维持 String(e) 口径不变)
336
+ // car doctor」可达用户;非 CarM8Error 维持 String(e) 口径不变);
337
+ // 1.23-W1(D-W1-3):+kind 失败细类(加法尾键——B080001 多形态分型,审计 F-W0-2)
329
338
  const m8 = e instanceof CarM8Error
330
339
  const detail = m8 ? `${e.slug}: ${e.message}` : String(e)
331
340
  const seq = closeTurn({
332
341
  reason: 'error', detail,
333
- ...(m8 ? { code: e.code, userHint: e.userHint, retryable: e.retryable } : {}),
342
+ ...(m8 ? { code: e.code, userHint: e.userHint, retryable: e.retryable, ...(e.kind ? { kind: e.kind } : {}) } : {}),
334
343
  }).seq
335
344
  trail.push({ seq, reason: 'error' })
336
345
  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
- // 1.4-S4(D-19):chunk 间隙取消——此刻 assistant 尚未落链(流完才 append),无半截消息
137
- if (opts.signal?.aborted) throw new TurnAborted()
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) errorDetail = chunk.error.message
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
- if (opts.signal?.aborted) throw new TurnAborted()
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
- throw new Error(`CAR-E-LLM-PROTO: 工具调用 ${c.name} 参数非法 JSON(finishReason=${stopReason},${errorDetail ?? '无附加错误'})`)
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
  }
@@ -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
- yield { finishReason: 'error', error: { code: 'B080001', message: `provider HTTP ${res.status}${text ? `:${text.slice(0, 200)}` : ''}` } }
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
- yield { finishReason: 'error', error: { code: 'B080001', message: '流结束未携带 finish_reason(AL-05)' } }
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, ...(finish === 'error' ? { error: { code: 'B080001', message: `未映射的 finish_reason "${raw}"` } } : {}) }
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, ...(finish === 'error' ? { error: { code: 'B080001', message: `未映射的 finish_reason "${raw}"` } } : {}) }
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
- ? { finishReason: finish }
840
- : { finishReason: 'error', error: { code: 'B080001', message: `未映射的 stop_reason "${raw}"` } }
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
- yield { finishReason: 'error', error: { code: 'B080001', message: `provider 流错误:${evt.error?.message ?? JSON.stringify(evt).slice(0, 200)}` } }
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
- error?: { code: string; message: string }
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 面捕获;
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"}}}