@lqc123qwe/car-runtime 1.19.0 → 1.20.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
- 49d18ea2a3d001d3c67c53bd63e422393a944fb4dece4f52c709c50ade2c40be car-runtime-1.19.0.tgz
1
+ 4546ebcc06ccdb9f33754d2d98ee413b25937f61e6bd3fe06a6cff60fa7ae541 car-runtime-1.20.0.tgz
Binary file
@@ -0,0 +1 @@
1
+ {"base64Signature":"MEYCIQCbYahnJCIA6I3JiR78DqF448xO747Dj5VSDZa+DjrhBwIhAOWOh2Ax0tWvzwsaV+OjU2P/tU5+zZ63vwIRgHQ+gSD4","cert":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUc1ekNDQm0yZ0F3SUJBZ0lVQm5MRi9MSmhtbDh6b2p3Q3NIM1Z3aGtzWGJnd0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpZeE1UQTNNRGMwTXpBMFdoY05Nall4TVRBM01EYzFNekEwV2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUV2WkJzSDB0WlFzZkh0VzNhZUordVVKaVA2dWJKT0lVSlZnbisKRjFkaWNuK2xlQVo4Uk80UDFUVm5QemFMdlNVNkdaL3dZcGtZWjlPZ0RDOGt2bWk5SDZPQ0JZd3dnZ1dJTUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWZCZ05WSFNNRUdEQVdnQlRmCjArblBWaVFSbHZtbzJPa29WYUxHTGhoa1B6QmlCZ05WSFJFQkFmOEVXREJXaGxSb2RIUndjem92TDJkcGRHaDEKWWk1amIyMHZiSEZqTmpJMEwyTmhjaTF5ZFc1MGFXMWxMeTVuYVhSb2RXSXZkMjl5YTJac2IzZHpMM0psYkdWaApjMlV1ZVcxc1FISmxabk12ZEdGbmN5OHhMakl3TGpBd09RWUtLd1lCQkFHRHZ6QUJBUVFyYUhSMGNITTZMeTkwCmIydGxiaTVoWTNScGIyNXpMbWRwZEdoMVluVnpaWEpqYjI1MFpXNTBMbU52YlRBVkJnb3JCZ0VFQVlPL01BRUMKQkFkeVpXeGxZWE5sTURZR0Npc0dBUVFCZzc4d0FRTUVLR1l6TUdVME5qaGxNRFEyTkRjNVl6QmxOMkUyWm1VdwpaVFE0WVdNeVpUVmhNbUUzTlRoaVpEa3dGUVlLS3dZQkJBR0R2ekFCQkFRSGNtVnNaV0Z6WlRBZ0Jnb3JCZ0VFCkFZTy9NQUVGQkJKc2NXTTJNalF2WTJGeUxYSjFiblJwYldVd0hnWUtLd1lCQkFHRHZ6QUJCZ1FRY21WbWN5OTAKWVdkekx6RXVNakF1TURBN0Jnb3JCZ0VFQVlPL01BRUlCQzBNSzJoMGRIQnpPaTh2ZEc5clpXNHVZV04wYVc5dQpjeTVuYVhSb2RXSjFjMlZ5WTI5dWRHVnVkQzVqYjIwd1pBWUtLd1lCQkFHRHZ6QUJDUVJXREZSb2RIUndjem92CkwyZHBkR2gxWWk1amIyMHZiSEZqTmpJMEwyTmhjaTF5ZFc1MGFXMWxMeTVuYVhSb2RXSXZkMjl5YTJac2IzZHoKTDNKbGJHVmhjMlV1ZVcxc1FISmxabk12ZEdGbmN5OHhMakl3TGpBd09BWUtLd1lCQkFHRHZ6QUJDZ1FxRENobQpNekJsTkRZNFpUQTBOalEzT1dNd1pUZGhObVpsTUdVME9HRmpNbVUxWVRKaE56VTRZbVE1TUIwR0Npc0dBUVFCCmc3OHdBUXNFRHd3TloybDBhSFZpTFdodmMzUmxaREExQmdvckJnRUVBWU8vTUFFTUJDY01KV2gwZEhCek9pOHYKWjJsMGFIVmlMbU52YlM5c2NXTTJNalF2WTJGeUxYSjFiblJwYldVd09BWUtLd1lCQkFHRHZ6QUJEUVFxRENobQpNekJsTkRZNFpUQTBOalEzT1dNd1pUZGhObVpsTUdVME9HRmpNbVUxWVRKaE56VTRZbVE1TUNBR0Npc0dBUVFCCmc3OHdBUTRFRWd3UWNtVm1jeTkwWVdkekx6RXVNakF1TURBYUJnb3JCZ0VFQVlPL01BRVBCQXdNQ2pFek5qRXgKT1RJM01UUXdLUVlLS3dZQkJBR0R2ekFCRUFRYkRCbG9kSFJ3Y3pvdkwyZHBkR2gxWWk1amIyMHZiSEZqTmpJMApNQmdHQ2lzR0FRUUJnNzh3QVJFRUNnd0lOamt5TXpRMU5qUXdaQVlLS3dZQkJBR0R2ekFCRWdSV0RGUm9kSFJ3CmN6b3ZMMmRwZEdoMVlpNWpiMjB2YkhGak5qSTBMMk5oY2kxeWRXNTBhVzFsTHk1bmFYUm9kV0l2ZDI5eWEyWnMKYjNkekwzSmxiR1ZoYzJVdWVXMXNRSEpsWm5NdmRHRm5jeTh4TGpJd0xqQXdPQVlLS3dZQkJBR0R2ekFCRXdRcQpEQ2htTXpCbE5EWTRaVEEwTmpRM09XTXdaVGRoTm1abE1HVTBPR0ZqTW1VMVlUSmhOelU0WW1RNU1CY0dDaXNHCkFRUUJnNzh3QVJRRUNRd0hjbVZzWldGelpUQlpCZ29yQmdFRUFZTy9NQUVWQkVzTVNXaDBkSEJ6T2k4dloybDAKYUhWaUxtTnZiUzlzY1dNMk1qUXZZMkZ5TFhKMWJuUnBiV1V2WVdOMGFXOXVjeTl5ZFc1ekx6UXlNelV5TkRJeApPRFl4TDJGMGRHVnRjSFJ6THpFd0ZnWUtLd1lCQkFHRHZ6QUJGZ1FJREFad2RXSnNhV013VUFZS0t3WUJCQUdECnZ6QUJHQVJDREVCeVpYQnZPbXh4WXpZeU5FQTJPVEl6TkRVMk5DOWpZWEl0Y25WdWRHbHRaVUF4TXpZeE1Ua3kKTnpFME9uSmxaanB5WldaekwzUmhaM012TVM0eU1DNHdNSUdLQmdvckJnRUVBZFo1QWdRQ0JId0VlZ0I0QUhZQQozVDB3YXNiSEVUSmpHUjRjbVdjM0FxSktYcmplUEszL2g0cHlnQzhwN280QUFBR2h0UFlEeXdBQUJBTUFSekJGCkFpRUFrQlNnbEJvOThmMVZNMFMyUGhaUnlQWG0yZ1pFWUFSREZnY1JsenlObXprQ0lDUWc2NUgxL1diUjJHUEkKYmk0MlFDVGdhKzlsMDduYU1rTG8zTm5NbDN6eE1Bb0dDQ3FHU000OUJBTURBMmdBTUdVQ01RRGxjdWNsQjhvTwpzVE0reGJ0ems3Z0hEbXhOZ2gzbEEwaHk4NUFaWUdIZG5PSUI5N0JLMGtjcWFWMnhPSUg1SVRnQ01FQTNmU293CkppaUUxbXNVNUlRSGc5V2pDcTB5dkpoNmc1VW5LVDFwY1RFN3FNSCtIYVU0WE0wWHVYK3dHWWpjNGc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==","rekorBundle":{"SignedEntryTimestamp":"MEUCIQDB6YqP2KWVy4Fo7LbSrDV7nIEb4z/smHLueDq0QzfuQQIgc0BoLANFB0BT/TS7PHukqcqBjRM0g3tV9AA+Kbq5Jvo=","Payload":{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI0NTQ2ZWJjYzA2Y2NkYjlmMzM3NTRkMmQ5OGVlNDEzYjI1OTM3ZjYxZTZiZDNmZTA2YTZjZmY2MGZhN2FlNTQxIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FWUNJUUNiWWFobkpDSUE2STNKaVI3OERxRjQ0OHhPNzQ3RGo1VlNEWmErRGpyaEJ3SWhBT1dPaDJBeDB0V3Z6d3NhVitPalUyUC90VTUrelo2M3Z3SVJnSFErZ1NENCIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVYzFla05EUW0weVowRjNTVUpCWjBsVlFtNU1SaTlNU21odGJEaDZiMnAzUTNOSU0xWjNhR3R6V0dKbmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFplRTFVUVROTlJHTXdUWHBCTUZkb1kwNU5hbGw0VFZSQk0wMUVZekZOZWtFd1YycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVYyV2tKelNEQjBXbEZ6WmtoMFZ6TmhaVW9yZFZWS2FWQTJkV0pLVDBsVlNsWm5iaXNLUmpGa2FXTnVLMnhsUVZvNFVrODBVREZVVm01UWVtRk1kbE5WTmtkYUwzZFpjR3RaV2psUFowUkRPR3QyYldrNVNEWlBRMEpaZDNkbloxZEpUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXWkNaMDVXU0ZOTlJVZEVRVmRuUWxSbUNqQXJibEJXYVZGU2JIWnRiekpQYTI5V1lVeEhUR2hvYTFCNlFtbENaMDVXU0ZKRlFrRm1PRVZYUkVKWGFHeFNiMlJJVW5kamVtOTJUREprY0dSSGFERUtXV2sxYW1JeU1IWmlTRVpxVG1wSk1Fd3lUbWhqYVRGNVpGYzFNR0ZYTVd4TWVUVnVZVmhTYjJSWFNYWmtNamw1WVRKYWMySXpaSHBNTTBwc1lrZFdhQXBqTWxWMVpWY3hjMUZJU214YWJrMTJaRWRHYm1ONU9IaE1ha2wzVEdwQmQwOVJXVXRMZDFsQ1FrRkhSSFo2UVVKQlVWRnlZVWhTTUdOSVRUWk1lVGt3Q21JeWRHeGlhVFZvV1ROU2NHSXlOWHBNYldSd1pFZG9NVmx1Vm5wYVdFcHFZakkxTUZwWE5UQk1iVTUyWWxSQlZrSm5iM0pDWjBWRlFWbFBMMDFCUlVNS1FrRmtlVnBYZUd4WldFNXNUVVJaUjBOcGMwZEJVVkZDWnpjNGQwRlJUVVZMUjFsNlRVZFZNRTVxYUd4TlJGRXlUa1JqTlZsNlFteE9Na1V5V20xVmR3cGFWRkUwV1ZkTmVWcFVWbWhOYlVVelRsUm9hVnBFYTNkR1VWbExTM2RaUWtKQlIwUjJla0ZDUWtGUlNHTnRWbk5hVjBaNldsUkJaMEpuYjNKQ1owVkZDa0ZaVHk5TlFVVkdRa0pLYzJOWFRUSk5hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFZkMGhuV1V0TGQxbENRa0ZIUkhaNlFVSkNaMUZSWTIxV2JXTjVPVEFLV1Zka2VreDZSWFZOYWtGMVRVUkJOMEpuYjNKQ1owVkZRVmxQTDAxQlJVbENRekJOU3pKb01HUklRbnBQYVRoMlpFYzVjbHBYTkhWWlYwNHdZVmM1ZFFwamVUVnVZVmhTYjJSWFNqRmpNbFo1V1RJNWRXUkhWblZrUXpWcVlqSXdkMXBCV1V0TGQxbENRa0ZIUkhaNlFVSkRVVkpYUkVaU2IyUklVbmRqZW05MkNrd3laSEJrUjJneFdXazFhbUl5TUhaaVNFWnFUbXBKTUV3eVRtaGphVEY1WkZjMU1HRlhNV3hNZVRWdVlWaFNiMlJYU1haa01qbDVZVEphYzJJelpIb0tURE5LYkdKSFZtaGpNbFYxWlZjeGMxRklTbXhhYmsxMlpFZEdibU41T0hoTWFrbDNUR3BCZDA5QldVdExkMWxDUWtGSFJIWjZRVUpEWjFGeFJFTm9iUXBOZWtKc1RrUlpORnBVUVRCT2FsRXpUMWROZDFwVVpHaE9iVnBzVFVkVk1FOUhSbXBOYlZVeFdWUkthRTU2VlRSWmJWRTFUVUl3UjBOcGMwZEJVVkZDQ21jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacFRGZG9kbU16VW14YVJFRXhRbWR2Y2tKblJVVkJXVTh2VFVGRlRVSkRZMDFLVjJnd1pFaENlazlwT0hZS1dqSnNNR0ZJVm1sTWJVNTJZbE01YzJOWFRUSk5hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFZkMDlCV1V0TGQxbENRa0ZIUkhaNlFVSkVVVkZ4UkVOb2JRcE5la0pzVGtSWk5GcFVRVEJPYWxFelQxZE5kMXBVWkdoT2JWcHNUVWRWTUU5SFJtcE5iVlV4V1ZSS2FFNTZWVFJaYlZFMVRVTkJSME5wYzBkQlVWRkNDbWMzT0hkQlVUUkZSV2QzVVdOdFZtMWplVGt3V1Zka2VreDZSWFZOYWtGMVRVUkJZVUpuYjNKQ1owVkZRVmxQTDAxQlJWQkNRWGROUTJwRmVrNXFSWGdLVDFSSk0wMVVVWGRMVVZsTFMzZFpRa0pCUjBSMmVrRkNSVUZSWWtSQ2JHOWtTRkozWTNwdmRrd3laSEJrUjJneFdXazFhbUl5TUhaaVNFWnFUbXBKTUFwTlFtZEhRMmx6UjBGUlVVSm5OemgzUVZKRlJVTm5kMGxPYW10NVRYcFJNVTVxVVhkYVFWbExTM2RaUWtKQlIwUjJla0ZDUldkU1YwUkdVbTlrU0ZKM0NtTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJZa2hHYWs1cVNUQk1NazVvWTJreGVXUlhOVEJoVnpGc1RIazFibUZZVW05a1YwbDJaREk1ZVdFeVduTUtZak5rZWt3elNteGlSMVpvWXpKVmRXVlhNWE5SU0Vwc1dtNU5kbVJIUm01amVUaDRUR3BKZDB4cVFYZFBRVmxMUzNkWlFrSkJSMFIyZWtGQ1JYZFJjUXBFUTJodFRYcENiRTVFV1RSYVZFRXdUbXBSTTA5WFRYZGFWR1JvVG0xYWJFMUhWVEJQUjBacVRXMVZNVmxVU21oT2VsVTBXVzFSTlUxQ1kwZERhWE5IQ2tGUlVVSm5OemgzUVZKUlJVTlJkMGhqYlZaeldsZEdlbHBVUWxwQ1oyOXlRbWRGUlVGWlR5OU5RVVZXUWtWelRWTlhhREJrU0VKNlQyazRkbG95YkRBS1lVaFdhVXh0VG5aaVV6bHpZMWROTWsxcVVYWlpNa1o1VEZoS01XSnVVbkJpVjFWMldWZE9NR0ZYT1hWamVUbDVaRmMxZWt4NlVYbE5lbFY1VGtSSmVBcFBSRmw0VERKR01HUkhWblJqU0ZKNlRIcEZkMFpuV1V0TGQxbENRa0ZIUkhaNlFVSkdaMUZKUkVGYWQyUlhTbk5oVjAxM1ZVRlpTMHQzV1VKQ1FVZEVDblo2UVVKSFFWSkRSRVZDZVZwWVFuWlBiWGg0V1hwWmVVNUZRVEpQVkVsNlRrUlZNazVET1dwWldFbDBZMjVXZFdSSGJIUmFWVUY0VFhwWmVFMVVhM2tLVG5wRk1FOXVTbXhhYW5CNVdsZGFla3d6VW1oYU0wMTJUVk0wZVUxRE5IZE5TVWRMUW1kdmNrSm5SVVZCWkZvMVFXZFJRMEpJZDBWbFowSTBRVWhaUVFvelZEQjNZWE5pU0VWVVNtcEhValJqYlZkak0wRnhTa3RZY21wbFVFc3pMMmcwY0hsblF6aHdOMjgwUVVGQlIyaDBVRmxFZVhkQlFVSkJUVUZTZWtKR0NrRnBSVUZyUWxObmJFSnZPVGhtTVZaTk1GTXlVR2hhVW5sUVdHMHlaMXBGV1VGU1JFWm5ZMUpzZW5sT2JYcHJRMGxEVVdjMk5VZ3hMMWRpVWpKSFVFa0tZbWswTWxGRFZHZGhLemxzTURkdVlVMXJURzh6VG01TmJETjZlRTFCYjBkRFEzRkhVMDAwT1VKQlRVUkJNbWRCVFVkVlEwMVJSR3hqZFdOc1FqaHZUd3B6VkUwcmVHSjBlbXMzWjBoRWJYaE9aMmd6YkVFd2FIazROVUZhV1VkSVpHNVBTVUk1TjBKTE1HdGpjV0ZXTW5oUFNVZzFTVlJuUTAxRlFUTm1VMjkzQ2twcGFVVXhiWE5WTlVsUlNHYzVWMnBEY1RCNWRrcG9ObWMxVlc1TFZERndZMVJGTjNGTlNDdElZVlUwV0Uwd1dIVllLM2RIV1dwak5HYzlQUW90TFMwdExVVk9SQ0JEUlZKVVNVWkpRMEZVUlMwdExTMHRDZz09In19fX0=","integratedTime":1794037384,"logIndex":3732640096,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}}
package/dist/cli.js CHANGED
@@ -223,6 +223,17 @@ async function main() {
223
223
  // 池/单端点两分支通用,须先于适配器创建)
224
224
  if (rest.includes('--no-prompt-cache'))
225
225
  process.env.CAR_LLM_PROMPT_CACHE = 'off';
226
+ // 1.20-K1(D-48 P2):滚动断点白名单——flag > env > config(env = 适配器每请求求值的
227
+ // 运行时事实源;'off' = 显式清空;池成员身份在 pool 装配处按 m.id 传入,不走本 env 的单端点语义)
228
+ const rollingFlag = flagValue(rest, '--prompt-cache-rolling');
229
+ if (rollingFlag !== undefined)
230
+ process.env.CAR_LLM_PROMPT_CACHE_ROLLING = rollingFlag;
231
+ else if (!process.env.CAR_LLM_PROMPT_CACHE_ROLLING && llm.promptCache?.rolling?.adapters?.length) {
232
+ process.env.CAR_LLM_PROMPT_CACHE_ROLLING = llm.promptCache.rolling.adapters.join(',');
233
+ }
234
+ if (llm.promptCache?.rolling?.breakpoints !== undefined) {
235
+ process.env.CAR_LLM_PROMPT_CACHE_ROLLING_BREAKPOINTS = String(llm.promptCache.rolling.breakpoints);
236
+ }
226
237
  const credService = new CredentialService();
227
238
  const core = new RuntimeCore(credService);
228
239
  core.bindContext(ctx);
package/dist/dx/doctor.js CHANGED
@@ -87,12 +87,40 @@ export function doctorModelReadiness(opts = {}) {
87
87
  catch { /* 全落空 = not-found(登记口径,非错误) */ }
88
88
  }
89
89
  const configured = !!baseUrl && !!model;
90
+ // 1.20-K1(D-48 P2):提示缓存策略可视(断点 = 传输层注解;rolling = 白名单缺省关闭,
91
+ // 登记前置 = 该端点 rolling 形态探针 VERDICT=A——1.20 设计 §3.5 流程门控)
92
+ const cacheOff = env.CAR_LLM_PROMPT_CACHE === 'off';
93
+ const rollingEnv = env.CAR_LLM_PROMPT_CACHE_ROLLING;
94
+ const cfgRolling = cfg.error ? undefined : cfg.config.llm?.promptCache?.rolling;
95
+ const rollingList = rollingEnv !== undefined
96
+ ? rollingEnv.split(',').map(s => s.trim()).filter(Boolean)
97
+ : (cfgRolling?.adapters ?? []);
98
+ const rollingShown = rollingEnv !== undefined
99
+ ? rollingList
100
+ : rollingList.length ? rollingList : undefined;
101
+ // 白名单未匹配告警(fail-visible 不阻断——成员可能已删,但登记错误必须可见)
102
+ const knownIdentities = new Set();
103
+ if (!cfg.error && cfg.config.modelPool?.endpoints.length) {
104
+ cfg.config.modelPool.endpoints.forEach((ep, i) => knownIdentities.add(ep.id ?? `ep-${i + 1}`));
105
+ }
106
+ else if (adapterId === 'anthropic') {
107
+ knownIdentities.add('anthropic');
108
+ }
109
+ const unmatched = rollingList.filter(id => !knownIdentities.has(id));
110
+ const cacheParts = [
111
+ `promptCache=${cacheOff ? 'off' : 'on'}`,
112
+ rollingShown === undefined ? 'rolling=缺省关闭(白名单须探针 VERDICT=A 后登记——llm.promptCache.rolling.adapters / CAR_LLM_PROMPT_CACHE_ROLLING)'
113
+ : rollingShown.includes('off') ? 'rolling=off(显式清空)'
114
+ : `rolling=[${rollingShown.join(',')}]${cfgRolling?.breakpoints !== undefined ? ` breakpoints=${cfgRolling.breakpoints}` : ''}`,
115
+ unmatched.length ? `⚠️ 白名单含未匹配身份:${unmatched.join(',')}(无在册单端点 anthropic / 池成员 id 对应——登记错误或成员已删)` : '',
116
+ ].filter(Boolean);
90
117
  const parts = [
91
118
  configured ? `baseUrl=${baseUrl} model=${model}${adapterId ? ` adapter=${adapterId}` : ''}` : 'baseUrl/model 未配置(car run 真路径需 llm.* 配置或 CAR_LLM_BASE_URL/CAR_LLM_MODEL——见 car doctor / 配置指南)',
92
119
  credential === 'keychain' ? `凭据=keychain 命中(car-runtime/${provider},值不打印)`
93
120
  : credential === 'env' ? '凭据=env 命中(显式开启 fallback,值不打印)'
94
121
  : '凭据未就绪(keychain 未命中且 env fallback 未开或变量缺席——A080001 会引导至此)',
95
122
  `envFallback=${envFallback}`,
123
+ ...cacheParts,
96
124
  ];
97
125
  return { configured, baseUrl: baseUrl ?? '', model: model ?? '', credential, envFallback, detail: parts.join(';') };
98
126
  }
@@ -25,6 +25,17 @@ export interface LlmConfig {
25
25
  compaction?: CompactionConfig;
26
26
  /** 1.16-K3(D-K2-2):子 Agent 配置(缺省 = 继承父会话模型锚点 Q-A2 + 宿主护栏常量 Q-A3) */
27
27
  subagent?: SubagentConfig;
28
+ /** 1.20-K1(D-48 P2):提示缓存滚动断点白名单(缺省关闭;运行时事实源 = CAR_LLM_PROMPT_CACHE_ROLLING,cli 按 flag > env > config 落 env) */
29
+ promptCache?: PromptCacheConfig;
30
+ }
31
+ /** 1.20-K1(D-48 P2):滚动断点白名单(1.20 设计 §3.4/§3.5——逐条确证,无通配) */
32
+ export interface PromptCacheConfig {
33
+ rolling?: {
34
+ /** 路由身份白名单(单端点 'anthropic' / 池成员 id);空表显式拒绝(禁静默 no-op);登记前置 = 该端点探针 VERDICT=A */
35
+ adapters?: string[];
36
+ /** 滚动断点数(1..3 钳制;缺省 1——多挂对同一前缀重复写 1.25x 缓存,1.20 设计 §3.3) */
37
+ breakpoints?: number;
38
+ };
28
39
  }
29
40
  /** 1.16-K3(D-K2-2):子 Agent 配置面——宿主编排消费(内核自身不装配编排;schema 只登记键位) */
30
41
  export interface SubagentConfig {
@@ -148,6 +148,17 @@ const rawSchema = z.object({
148
148
  }).strict()
149
149
  .refine(v => Object.keys(v).length > 0, { message: '至少登记一个键(model/maxConcurrent/maxCumulative;无子 Agent 配置需求请移除 llm.subagent 键,行为与 1.15.2 一致)' })
150
150
  .optional(),
151
+ // 1.20-K1(D-48 P2):滚动断点白名单(路由身份精确匹配;adapters 空表显式拒——禁静默 no-op)
152
+ promptCache: z.object({
153
+ rolling: z.object({
154
+ adapters: z.array(nonEmptyTrimmed(), { invalid_type_error: '必须为字符串数组' })
155
+ .min(1, { message: '至少登记一个路由身份(空表 = 静默 no-op 显式拒绝;不需要滚动断点请移除 llm.promptCache 键)' })
156
+ .optional(),
157
+ breakpoints: positiveInt()
158
+ .refine(v => v <= 3, { message: '必须为 1..3(provider 断点硬限 4,system 占 1)' })
159
+ .optional(),
160
+ }).strict().optional(),
161
+ }).strict().optional(),
151
162
  }).strict().optional(),
152
163
  // 1.14-S4(K-POOL-1 / D-42):模型池登记(多端点 fallback + health + routing/decide 落链)
153
164
  modelPool: modelPoolSchema.optional(),
@@ -221,6 +232,18 @@ function parseCarConfig(raw) {
221
232
  sg.maxCumulative = d.llm.subagent.maxCumulative;
222
233
  llm.subagent = sg;
223
234
  }
235
+ if (d.llm.promptCache) {
236
+ const pc = {};
237
+ if (d.llm.promptCache.rolling) {
238
+ const r = {};
239
+ if (d.llm.promptCache.rolling.adapters !== undefined)
240
+ r.adapters = [...d.llm.promptCache.rolling.adapters];
241
+ if (d.llm.promptCache.rolling.breakpoints !== undefined)
242
+ r.breakpoints = d.llm.promptCache.rolling.breakpoints;
243
+ pc.rolling = r;
244
+ }
245
+ llm.promptCache = pc;
246
+ }
224
247
  out.llm = llm;
225
248
  }
226
249
  if (d.mcp) {
@@ -372,6 +395,8 @@ export function mergeLlmConfig(env, cfg, flags = {}) {
372
395
  ...(adapterId ? { adapterId } : {}),
373
396
  ...(maxTokens !== undefined ? { maxTokens } : {}),
374
397
  allowEnvFallback,
398
+ // 1.20-K1(D-48 P2):滚动断点声明层透传(cli 层按 flag > env > config 落运行时 env)
399
+ ...(cfg.llm?.promptCache ? { promptCache: cfg.llm.promptCache } : {}),
375
400
  };
376
401
  }
377
402
  /**
package/dist/load/pool.js CHANGED
@@ -226,6 +226,9 @@ export function assembleModelPool(opts) {
226
226
  ? createAnthropicAdapter({
227
227
  baseUrl: m.baseUrl,
228
228
  credentials: opts.credentials,
229
+ // 1.20-K1(D-48 P2):滚动断点按成员路由身份求值——池成员适配器 id 恒 'anthropic'
230
+ //(装配未透传),禁按其求值(一端点解锁 = 全池解锁,1.20 设计 §3.1)
231
+ rollingIdentity: m.id,
229
232
  ...(m.keyEnv !== undefined ? { credentialEnvVars: [m.keyEnv] } : {}),
230
233
  ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),
231
234
  ...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
@@ -1,4 +1,4 @@
1
- {"stage":"archive","detail":"git archive HEAD → car-runtime-1.19.0.tgz(421089 bytes,树哈希锚定)","ts":1793526334104}
2
- {"stage":"sha256sums","detail":"SHA256SUMS 49d18ea2a3d001d3…(裸字节复核通过)","ts":1793526334105}
3
- {"stage":"sbom","detail":"55 个组件(运行时依赖 0 项)","ts":1793526334112}
4
- {"stage":"cosign-keyless","detail":"signed car-runtime-1.19.0.tgz (keyless OIDC + Rekor)","ts":1793526339914}
1
+ {"stage":"archive","detail":"git archive HEAD → car-runtime-1.20.0.tgz(430265 bytes,树哈希锚定)","ts":1794037383291}
2
+ {"stage":"sha256sums","detail":"SHA256SUMS 4546ebcc06ccdb9f…(裸字节复核通过)","ts":1794037383292}
3
+ {"stage":"sbom","detail":"55 个组件(运行时依赖 0 项)","ts":1794037383299}
4
+ {"stage":"cosign-keyless","detail":"signed car-runtime-1.20.0.tgz (keyless OIDC + Rekor)","ts":1794037385104}
@@ -76,7 +76,21 @@ export interface AnthropicAdapterOptions {
76
76
  defaultMaxTokens?: number;
77
77
  /** 1.14-S4(K-POOL-1):成员级凭据 env 变量名引用(modelPool keyEnv——resolve 精确候选;keychain 链保持在前) */
78
78
  credentialEnvVars?: string[];
79
+ /**
80
+ * 1.20-K1(D-48 P2):滚动断点求值身份。缺省 = id(单端点 'anthropic');
81
+ * **池成员必须显式传 m.id**——池成员适配器 id 恒 'anthropic'(装配未透传成员标识),
82
+ * 按其求值 = 白名单一端点解锁全池(1.20 设计 §3.1 专项锁,S66/AC-9)。
83
+ */
84
+ rollingIdentity?: string;
79
85
  }
86
+ /**
87
+ * 1.20-K1(D-48 P2):滚动断点白名单求值(每请求求值,与 promptCacheOff 同址同时序)。
88
+ * 键 = 路由身份:单端点 = adapter id('anthropic')/ 池成员 = 显式 rollingIdentity(m.id)——
89
+ * **禁按 adapter.id 给池成员求值**(池成员适配器 id 恒 'anthropic'——pool 装配未透传成员标识,
90
+ * 一端点解锁 = 全池解锁,1.20 设计 §3.1)。off 连坐最高优先;清单值 'off' = 显式清空;
91
+ * 无 '*' 通配(通配废掉逐条确证);大小写敏感(归一化会掩盖登记错误)。
92
+ */
93
+ export declare function rollingCacheAllowed(identity: string): boolean;
80
94
  export declare function createAnthropicAdapter(opts: AnthropicAdapterOptions): LlmAdapter & {
81
95
  id: string;
82
96
  };
@@ -535,11 +535,68 @@ function mergeAnthropicUsage(start, delta, model, adapterId) {
535
535
  * 投影/快照比对,N1 零影响;生效有 provider 侧最小长度门槛(~1024 tokens)——短 system
536
536
  * 静默不生效应以 usage.cacheCreationTokens 观测,禁以「配了就该生效」推断。关断 =
537
537
  * CAR_LLM_PROMPT_CACHE=off(cli flag --no-prompt-cache 置本 env;A/B 与故障回退通道)。
538
+ * 1.20-K1(D-48 P2):滚动断点——白名单命中时在尾部消息挂第二处断点(缺省 1 个,只块化命中
539
+ * 消息),键 = 路由身份(rollingIdentity,池成员 = m.id);CAR_LLM_PROMPT_CACHE_ROLLING
540
+ * 逗号清单('off' = 显式清空,无通配)+ CAR_LLM_PROMPT_CACHE_ROLLING_BREAKPOINTS(1..3 钳制)。
538
541
  */
539
542
  function promptCacheOff() {
540
543
  return process.env.CAR_LLM_PROMPT_CACHE === 'off';
541
544
  }
542
- function toAnthropic(req, apiKey) {
545
+ /**
546
+ * 1.20-K1(D-48 P2):滚动断点白名单求值(每请求求值,与 promptCacheOff 同址同时序)。
547
+ * 键 = 路由身份:单端点 = adapter id('anthropic')/ 池成员 = 显式 rollingIdentity(m.id)——
548
+ * **禁按 adapter.id 给池成员求值**(池成员适配器 id 恒 'anthropic'——pool 装配未透传成员标识,
549
+ * 一端点解锁 = 全池解锁,1.20 设计 §3.1)。off 连坐最高优先;清单值 'off' = 显式清空;
550
+ * 无 '*' 通配(通配废掉逐条确证);大小写敏感(归一化会掩盖登记错误)。
551
+ */
552
+ export function rollingCacheAllowed(identity) {
553
+ if (promptCacheOff())
554
+ return false;
555
+ const raw = process.env.CAR_LLM_PROMPT_CACHE_ROLLING;
556
+ if (!raw)
557
+ return false;
558
+ const list = raw.split(',').map(s => s.trim()).filter(Boolean);
559
+ if (!list.length || list.includes('off'))
560
+ return false;
561
+ return list.includes(identity);
562
+ }
563
+ // 1.20-K1:滚动断点数——provider 硬限 4,system 断点(P1)恒占 1;缺省 1(多挂 = 对同一前缀
564
+ // 重复写 1.25x 缓存,1.20 设计 §3.3)。env 非法/缺席回落缺省,超限钳制(AC-8)
565
+ const ROLLING_BREAKPOINTS_DEFAULT = 1;
566
+ const ROLLING_BREAKPOINTS_MAX = 3;
567
+ function rollingBreakpoints() {
568
+ const raw = Number(process.env.CAR_LLM_PROMPT_CACHE_ROLLING_BREAKPOINTS);
569
+ if (!Number.isFinite(raw) || raw < 1)
570
+ return ROLLING_BREAKPOINTS_DEFAULT;
571
+ return Math.min(Math.trunc(raw), ROLLING_BREAKPOINTS_MAX);
572
+ }
573
+ /**
574
+ * 1.20-K1(D-48 P2):滚动断点放置——从尾部往前命中即挂,只块化被挂消息,其余消息维持
575
+ * 字符串(1.20 设计 §3.2:全量块化零收益高风险)。断点挂**末块**(tool_result / tool_use
576
+ * 多块形态不拆不重组,语义不变);空串 / 空数组 / 非对象末块无可挂载体 → 跳过前移(不换位不补造)。
577
+ * 纯传输层注解:只改本请求出站体,不进 SessionLog / 投影(N1 零影响,S59 同红线)。
578
+ */
579
+ function attachRollingBreakpoints(turns, count) {
580
+ let attached = 0;
581
+ for (let i = turns.length - 1; i >= 0 && attached < count; i--) {
582
+ const t = turns[i];
583
+ if (typeof t.content === 'string') {
584
+ if (!t.content.length)
585
+ continue;
586
+ t.content = [{ type: 'text', text: t.content, cache_control: { type: 'ephemeral' } }];
587
+ attached++;
588
+ continue;
589
+ }
590
+ if (Array.isArray(t.content) && t.content.length) {
591
+ const last = t.content[t.content.length - 1];
592
+ if (last && typeof last === 'object' && !Array.isArray(last)) {
593
+ last.cache_control = { type: 'ephemeral' };
594
+ attached++;
595
+ }
596
+ }
597
+ }
598
+ }
599
+ function toAnthropic(req, apiKey, cache) {
543
600
  const system = [];
544
601
  const turns = [];
545
602
  let pendingToolResults = [];
@@ -574,6 +631,10 @@ function toAnthropic(req, apiKey) {
574
631
  }
575
632
  }
576
633
  flushToolResults();
634
+ // 1.20-K1(D-48 P2):滚动断点在白名单命中时挂尾部消息(缺省 1 个)——system 断点(P1)
635
+ // 与消息断点独立,off 时两者俱灭(rollingCacheAllowed 内已连坐)
636
+ if (cache.rolling)
637
+ attachRollingBreakpoints(turns, cache.rollingBreakpoints);
577
638
  // 1.14-S1(K-MP-1 / D-39):思考参数——effort → thinking.budget_tokens(ANTHROPIC_THINKING_BUDGET 表);
578
639
  // off/缺席不下发;budget ≥ max_tokens 违反 Messages API 约束(max_tokens 必须大于 budget_tokens)→
579
640
  // 显式报错提示调高(fail-visible,禁静默改写 max_tokens)
@@ -687,6 +748,8 @@ async function* parseAnthropicSseStream(body, model, adapterId) {
687
748
  }
688
749
  export function createAnthropicAdapter(opts) {
689
750
  const id = opts.id ?? 'anthropic';
751
+ // 1.20-K1(D-48 P2):滚动断点求值身份(每请求经 rollingCacheAllowed 对 env 求值,非构造期定值)
752
+ const rollingIdentity = opts.rollingIdentity ?? id;
690
753
  const baseUrl = opts.baseUrl.replace(/\/+$/, '');
691
754
  assertTlsOrLoopback(baseUrl);
692
755
  const url = `${baseUrl}/v1/messages`;
@@ -714,7 +777,10 @@ export function createAnthropicAdapter(opts) {
714
777
  const ref = opts.credentials.resolve(opts.provider ?? id, credResolveOpts);
715
778
  apiKey = opts.credentials.reveal(ref, {});
716
779
  }
717
- return toAnthropic(req, apiKey);
780
+ return toAnthropic(req, apiKey, {
781
+ rolling: rollingCacheAllowed(rollingIdentity),
782
+ rollingBreakpoints: rollingBreakpoints(),
783
+ });
718
784
  },
719
785
  fetchImpl,
720
786
  signal: req.signal,
package/dist/sbom.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "bomFormat": "CAR-SBOM",
3
3
  "specVersion": "0.1",
4
- "version": "1.19.0",
4
+ "version": "1.20.0",
5
5
  "components": [
6
6
  {
7
7
  "type": "file",
@@ -11,12 +11,12 @@
11
11
  {
12
12
  "type": "file",
13
13
  "path": "src/cli.ts",
14
- "sha256": "833d9d33e281115d1ae50858028ce82b930a530b9ef513232f02bf3b94269fcb"
14
+ "sha256": "914df86dd59907d6aa24b3d339ee1e4c6bf366a63ac553199ad172760aa29539"
15
15
  },
16
16
  {
17
17
  "type": "file",
18
18
  "path": "src/dx/doctor.ts",
19
- "sha256": "bf0e39f62edbafe12f91100c009879cbc73ff1cd7628a7851b264dbef1fd5a4c"
19
+ "sha256": "32ea488aea0dcff1709f0244eff5d417b964e6f2eef5abab5a21da883da2e059"
20
20
  },
21
21
  {
22
22
  "type": "file",
@@ -56,7 +56,7 @@
56
56
  {
57
57
  "type": "file",
58
58
  "path": "src/load/config.ts",
59
- "sha256": "529e2c5d000e9cdb8835a554263c4f8881a1eed594176571c5eca53dddd17f47"
59
+ "sha256": "39f0185b46ed580ee3b9cac6a028fe6a3eddbf6257087bc0406b11f3e1b94d86"
60
60
  },
61
61
  {
62
62
  "type": "file",
@@ -66,7 +66,7 @@
66
66
  {
67
67
  "type": "file",
68
68
  "path": "src/load/pool.ts",
69
- "sha256": "e52909bcad4ba2270e62cb1f509abb6fd6e2e017e2f1b0ce8f326ade8d876ee3"
69
+ "sha256": "08595f3786eec9318644f2b183dad6f5d6db3cf90975a92bd4887a1d6591de62"
70
70
  },
71
71
  {
72
72
  "type": "file",
@@ -176,7 +176,7 @@
176
176
  {
177
177
  "type": "file",
178
178
  "path": "src/runtime-core/llm.ts",
179
- "sha256": "7aadf5f673e4db34c0ae45e44c48e9b9c7f36a970a0dd4d36285dc5ac74fa32f"
179
+ "sha256": "ea7d8f9c9e8b79d5f073df06e7e8cc8af1819500222108061ff07b60aa29695a"
180
180
  },
181
181
  {
182
182
  "type": "file",
@@ -271,7 +271,7 @@
271
271
  {
272
272
  "type": "file",
273
273
  "path": "package.json",
274
- "sha256": "7f407eeeabf8565fbe5340ef8c3041ba167b082937b2544a751247a36b7ec7a5"
274
+ "sha256": "29ba6c8cb059bcb1429d80a909ca79573c644ae1429f870a6065903c898d2816"
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.19.0",
3
+ "version": "1.20.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/cli.ts CHANGED
@@ -211,6 +211,16 @@ async function main(): Promise<number> {
211
211
  // 1.17-K4(D-48 P1 / V1-A):提示缓存断点关断 flag——flag > env(置 env 后适配器内部决策;
212
212
  // 池/单端点两分支通用,须先于适配器创建)
213
213
  if (rest.includes('--no-prompt-cache')) process.env.CAR_LLM_PROMPT_CACHE = 'off'
214
+ // 1.20-K1(D-48 P2):滚动断点白名单——flag > env > config(env = 适配器每请求求值的
215
+ // 运行时事实源;'off' = 显式清空;池成员身份在 pool 装配处按 m.id 传入,不走本 env 的单端点语义)
216
+ const rollingFlag = flagValue(rest, '--prompt-cache-rolling')
217
+ if (rollingFlag !== undefined) process.env.CAR_LLM_PROMPT_CACHE_ROLLING = rollingFlag
218
+ else if (!process.env.CAR_LLM_PROMPT_CACHE_ROLLING && llm.promptCache?.rolling?.adapters?.length) {
219
+ process.env.CAR_LLM_PROMPT_CACHE_ROLLING = llm.promptCache.rolling.adapters.join(',')
220
+ }
221
+ if (llm.promptCache?.rolling?.breakpoints !== undefined) {
222
+ process.env.CAR_LLM_PROMPT_CACHE_ROLLING_BREAKPOINTS = String(llm.promptCache.rolling.breakpoints)
223
+ }
214
224
  const credService = new CredentialService()
215
225
  const core = new RuntimeCore(credService)
216
226
  core.bindContext(ctx)
package/src/dx/doctor.ts CHANGED
@@ -123,12 +123,39 @@ export function doctorModelReadiness(opts: { env?: NodeJS.ProcessEnv; cwd?: stri
123
123
  } catch { /* 全落空 = not-found(登记口径,非错误) */ }
124
124
  }
125
125
  const configured = !!baseUrl && !!model
126
+ // 1.20-K1(D-48 P2):提示缓存策略可视(断点 = 传输层注解;rolling = 白名单缺省关闭,
127
+ // 登记前置 = 该端点 rolling 形态探针 VERDICT=A——1.20 设计 §3.5 流程门控)
128
+ const cacheOff = env.CAR_LLM_PROMPT_CACHE === 'off'
129
+ const rollingEnv = env.CAR_LLM_PROMPT_CACHE_ROLLING
130
+ const cfgRolling = cfg.error ? undefined : cfg.config.llm?.promptCache?.rolling
131
+ const rollingList = rollingEnv !== undefined
132
+ ? rollingEnv.split(',').map(s => s.trim()).filter(Boolean)
133
+ : (cfgRolling?.adapters ?? [])
134
+ const rollingShown = rollingEnv !== undefined
135
+ ? rollingList
136
+ : rollingList.length ? rollingList : undefined
137
+ // 白名单未匹配告警(fail-visible 不阻断——成员可能已删,但登记错误必须可见)
138
+ const knownIdentities = new Set<string>()
139
+ if (!cfg.error && cfg.config.modelPool?.endpoints.length) {
140
+ cfg.config.modelPool.endpoints.forEach((ep, i) => knownIdentities.add(ep.id ?? `ep-${i + 1}`))
141
+ } else if (adapterId === 'anthropic') {
142
+ knownIdentities.add('anthropic')
143
+ }
144
+ const unmatched = rollingList.filter(id => !knownIdentities.has(id))
145
+ const cacheParts = [
146
+ `promptCache=${cacheOff ? 'off' : 'on'}`,
147
+ rollingShown === undefined ? 'rolling=缺省关闭(白名单须探针 VERDICT=A 后登记——llm.promptCache.rolling.adapters / CAR_LLM_PROMPT_CACHE_ROLLING)'
148
+ : rollingShown.includes('off') ? 'rolling=off(显式清空)'
149
+ : `rolling=[${rollingShown.join(',')}]${cfgRolling?.breakpoints !== undefined ? ` breakpoints=${cfgRolling.breakpoints}` : ''}`,
150
+ unmatched.length ? `⚠️ 白名单含未匹配身份:${unmatched.join(',')}(无在册单端点 anthropic / 池成员 id 对应——登记错误或成员已删)` : '',
151
+ ].filter(Boolean)
126
152
  const parts = [
127
153
  configured ? `baseUrl=${baseUrl} model=${model}${adapterId ? ` adapter=${adapterId}` : ''}` : 'baseUrl/model 未配置(car run 真路径需 llm.* 配置或 CAR_LLM_BASE_URL/CAR_LLM_MODEL——见 car doctor / 配置指南)',
128
154
  credential === 'keychain' ? `凭据=keychain 命中(car-runtime/${provider},值不打印)`
129
155
  : credential === 'env' ? '凭据=env 命中(显式开启 fallback,值不打印)'
130
156
  : '凭据未就绪(keychain 未命中且 env fallback 未开或变量缺席——A080001 会引导至此)',
131
157
  `envFallback=${envFallback}`,
158
+ ...cacheParts,
132
159
  ]
133
160
  return { configured, baseUrl: baseUrl ?? '', model: model ?? '', credential, envFallback, detail: parts.join(';') }
134
161
  }
@@ -56,6 +56,18 @@ export interface LlmConfig {
56
56
  compaction?: CompactionConfig
57
57
  /** 1.16-K3(D-K2-2):子 Agent 配置(缺省 = 继承父会话模型锚点 Q-A2 + 宿主护栏常量 Q-A3) */
58
58
  subagent?: SubagentConfig
59
+ /** 1.20-K1(D-48 P2):提示缓存滚动断点白名单(缺省关闭;运行时事实源 = CAR_LLM_PROMPT_CACHE_ROLLING,cli 按 flag > env > config 落 env) */
60
+ promptCache?: PromptCacheConfig
61
+ }
62
+
63
+ /** 1.20-K1(D-48 P2):滚动断点白名单(1.20 设计 §3.4/§3.5——逐条确证,无通配) */
64
+ export interface PromptCacheConfig {
65
+ rolling?: {
66
+ /** 路由身份白名单(单端点 'anthropic' / 池成员 id);空表显式拒绝(禁静默 no-op);登记前置 = 该端点探针 VERDICT=A */
67
+ adapters?: string[]
68
+ /** 滚动断点数(1..3 钳制;缺省 1——多挂对同一前缀重复写 1.25x 缓存,1.20 设计 §3.3) */
69
+ breakpoints?: number
70
+ }
59
71
  }
60
72
 
61
73
  /** 1.16-K3(D-K2-2):子 Agent 配置面——宿主编排消费(内核自身不装配编排;schema 只登记键位) */
@@ -264,6 +276,17 @@ const rawSchema = z.object({
264
276
  }).strict()
265
277
  .refine(v => Object.keys(v).length > 0, { message: '至少登记一个键(model/maxConcurrent/maxCumulative;无子 Agent 配置需求请移除 llm.subagent 键,行为与 1.15.2 一致)' })
266
278
  .optional(),
279
+ // 1.20-K1(D-48 P2):滚动断点白名单(路由身份精确匹配;adapters 空表显式拒——禁静默 no-op)
280
+ promptCache: z.object({
281
+ rolling: z.object({
282
+ adapters: z.array(nonEmptyTrimmed(), { invalid_type_error: '必须为字符串数组' })
283
+ .min(1, { message: '至少登记一个路由身份(空表 = 静默 no-op 显式拒绝;不需要滚动断点请移除 llm.promptCache 键)' })
284
+ .optional(),
285
+ breakpoints: positiveInt()
286
+ .refine(v => v <= 3, { message: '必须为 1..3(provider 断点硬限 4,system 占 1)' })
287
+ .optional(),
288
+ }).strict().optional(),
289
+ }).strict().optional(),
267
290
  }).strict().optional(),
268
291
  // 1.14-S4(K-POOL-1 / D-42):模型池登记(多端点 fallback + health + routing/decide 落链)
269
292
  modelPool: modelPoolSchema.optional(),
@@ -324,6 +347,16 @@ function parseCarConfig(raw: unknown): { config: CarConfig; error?: string } {
324
347
  if (d.llm.subagent.maxCumulative !== undefined) sg.maxCumulative = d.llm.subagent.maxCumulative
325
348
  llm.subagent = sg
326
349
  }
350
+ if (d.llm.promptCache) {
351
+ const pc: PromptCacheConfig = {}
352
+ if (d.llm.promptCache.rolling) {
353
+ const r: NonNullable<PromptCacheConfig['rolling']> = {}
354
+ if (d.llm.promptCache.rolling.adapters !== undefined) r.adapters = [...d.llm.promptCache.rolling.adapters]
355
+ if (d.llm.promptCache.rolling.breakpoints !== undefined) r.breakpoints = d.llm.promptCache.rolling.breakpoints
356
+ pc.rolling = r
357
+ }
358
+ llm.promptCache = pc
359
+ }
327
360
  out.llm = llm
328
361
  }
329
362
  if (d.mcp) {
@@ -469,6 +502,8 @@ export function mergeLlmConfig(env: NodeJS.ProcessEnv, cfg: CarConfig, flags: {
469
502
  ...(adapterId ? { adapterId } : {}),
470
503
  ...(maxTokens !== undefined ? { maxTokens } : {}),
471
504
  allowEnvFallback,
505
+ // 1.20-K1(D-48 P2):滚动断点声明层透传(cli 层按 flag > env > config 落运行时 env)
506
+ ...(cfg.llm?.promptCache ? { promptCache: cfg.llm.promptCache } : {}),
472
507
  }
473
508
  }
474
509
 
package/src/load/pool.ts CHANGED
@@ -304,6 +304,9 @@ export function assembleModelPool(opts: ModelPoolAssemblyOptions): {
304
304
  ? createAnthropicAdapter({
305
305
  baseUrl: m.baseUrl,
306
306
  credentials: opts.credentials,
307
+ // 1.20-K1(D-48 P2):滚动断点按成员路由身份求值——池成员适配器 id 恒 'anthropic'
308
+ //(装配未透传),禁按其求值(一端点解锁 = 全池解锁,1.20 设计 §3.1)
309
+ rollingIdentity: m.id,
307
310
  ...(m.keyEnv !== undefined ? { credentialEnvVars: [m.keyEnv] } : {}),
308
311
  ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),
309
312
  ...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
@@ -520,6 +520,12 @@ export interface AnthropicAdapterOptions {
520
520
  defaultMaxTokens?: number
521
521
  /** 1.14-S4(K-POOL-1):成员级凭据 env 变量名引用(modelPool keyEnv——resolve 精确候选;keychain 链保持在前) */
522
522
  credentialEnvVars?: string[]
523
+ /**
524
+ * 1.20-K1(D-48 P2):滚动断点求值身份。缺省 = id(单端点 'anthropic');
525
+ * **池成员必须显式传 m.id**——池成员适配器 id 恒 'anthropic'(装配未透传成员标识),
526
+ * 按其求值 = 白名单一端点解锁全池(1.20 设计 §3.1 专项锁,S66/AC-9)。
527
+ */
528
+ rollingIdentity?: string
523
529
  }
524
530
 
525
531
  /** stop_reason → CAR finishReason(未映射 fail-visible——openai FINISH_MAP 同纪律) */
@@ -579,12 +585,68 @@ function mergeAnthropicUsage(
579
585
  * 投影/快照比对,N1 零影响;生效有 provider 侧最小长度门槛(~1024 tokens)——短 system
580
586
  * 静默不生效应以 usage.cacheCreationTokens 观测,禁以「配了就该生效」推断。关断 =
581
587
  * CAR_LLM_PROMPT_CACHE=off(cli flag --no-prompt-cache 置本 env;A/B 与故障回退通道)。
588
+ * 1.20-K1(D-48 P2):滚动断点——白名单命中时在尾部消息挂第二处断点(缺省 1 个,只块化命中
589
+ * 消息),键 = 路由身份(rollingIdentity,池成员 = m.id);CAR_LLM_PROMPT_CACHE_ROLLING
590
+ * 逗号清单('off' = 显式清空,无通配)+ CAR_LLM_PROMPT_CACHE_ROLLING_BREAKPOINTS(1..3 钳制)。
582
591
  */
583
592
  function promptCacheOff(): boolean {
584
593
  return process.env.CAR_LLM_PROMPT_CACHE === 'off'
585
594
  }
586
595
 
587
- function toAnthropic(req: LlmRequest, apiKey: string) {
596
+ /**
597
+ * 1.20-K1(D-48 P2):滚动断点白名单求值(每请求求值,与 promptCacheOff 同址同时序)。
598
+ * 键 = 路由身份:单端点 = adapter id('anthropic')/ 池成员 = 显式 rollingIdentity(m.id)——
599
+ * **禁按 adapter.id 给池成员求值**(池成员适配器 id 恒 'anthropic'——pool 装配未透传成员标识,
600
+ * 一端点解锁 = 全池解锁,1.20 设计 §3.1)。off 连坐最高优先;清单值 'off' = 显式清空;
601
+ * 无 '*' 通配(通配废掉逐条确证);大小写敏感(归一化会掩盖登记错误)。
602
+ */
603
+ export function rollingCacheAllowed(identity: string): boolean {
604
+ if (promptCacheOff()) return false
605
+ const raw = process.env.CAR_LLM_PROMPT_CACHE_ROLLING
606
+ if (!raw) return false
607
+ const list = raw.split(',').map(s => s.trim()).filter(Boolean)
608
+ if (!list.length || list.includes('off')) return false
609
+ return list.includes(identity)
610
+ }
611
+
612
+ // 1.20-K1:滚动断点数——provider 硬限 4,system 断点(P1)恒占 1;缺省 1(多挂 = 对同一前缀
613
+ // 重复写 1.25x 缓存,1.20 设计 §3.3)。env 非法/缺席回落缺省,超限钳制(AC-8)
614
+ const ROLLING_BREAKPOINTS_DEFAULT = 1
615
+ const ROLLING_BREAKPOINTS_MAX = 3
616
+
617
+ function rollingBreakpoints(): number {
618
+ const raw = Number(process.env.CAR_LLM_PROMPT_CACHE_ROLLING_BREAKPOINTS)
619
+ if (!Number.isFinite(raw) || raw < 1) return ROLLING_BREAKPOINTS_DEFAULT
620
+ return Math.min(Math.trunc(raw), ROLLING_BREAKPOINTS_MAX)
621
+ }
622
+
623
+ /**
624
+ * 1.20-K1(D-48 P2):滚动断点放置——从尾部往前命中即挂,只块化被挂消息,其余消息维持
625
+ * 字符串(1.20 设计 §3.2:全量块化零收益高风险)。断点挂**末块**(tool_result / tool_use
626
+ * 多块形态不拆不重组,语义不变);空串 / 空数组 / 非对象末块无可挂载体 → 跳过前移(不换位不补造)。
627
+ * 纯传输层注解:只改本请求出站体,不进 SessionLog / 投影(N1 零影响,S59 同红线)。
628
+ */
629
+ function attachRollingBreakpoints(turns: Array<{ role: 'user' | 'assistant'; content: unknown }>, count: number): void {
630
+ let attached = 0
631
+ for (let i = turns.length - 1; i >= 0 && attached < count; i--) {
632
+ const t = turns[i]!
633
+ if (typeof t.content === 'string') {
634
+ if (!t.content.length) continue
635
+ t.content = [{ type: 'text', text: t.content, cache_control: { type: 'ephemeral' } }]
636
+ attached++
637
+ continue
638
+ }
639
+ if (Array.isArray(t.content) && t.content.length) {
640
+ const last = t.content[t.content.length - 1] as Record<string, unknown> | undefined
641
+ if (last && typeof last === 'object' && !Array.isArray(last)) {
642
+ last.cache_control = { type: 'ephemeral' }
643
+ attached++
644
+ }
645
+ }
646
+ }
647
+ }
648
+
649
+ function toAnthropic(req: LlmRequest, apiKey: string, cache: { rolling: boolean; rollingBreakpoints: number }) {
588
650
  const system: string[] = []
589
651
  const turns: Array<{ role: 'user' | 'assistant'; content: unknown }> = []
590
652
  let pendingToolResults: Array<Record<string, unknown>> = []
@@ -617,6 +679,9 @@ function toAnthropic(req: LlmRequest, apiKey: string) {
617
679
  }
618
680
  }
619
681
  flushToolResults()
682
+ // 1.20-K1(D-48 P2):滚动断点在白名单命中时挂尾部消息(缺省 1 个)——system 断点(P1)
683
+ // 与消息断点独立,off 时两者俱灭(rollingCacheAllowed 内已连坐)
684
+ if (cache.rolling) attachRollingBreakpoints(turns, cache.rollingBreakpoints)
620
685
  // 1.14-S1(K-MP-1 / D-39):思考参数——effort → thinking.budget_tokens(ANTHROPIC_THINKING_BUDGET 表);
621
686
  // off/缺席不下发;budget ≥ max_tokens 违反 Messages API 约束(max_tokens 必须大于 budget_tokens)→
622
687
  // 显式报错提示调高(fail-visible,禁静默改写 max_tokens)
@@ -718,6 +783,8 @@ async function* parseAnthropicSseStream(body: AsyncIterable<Uint8Array>, model:
718
783
 
719
784
  export function createAnthropicAdapter(opts: AnthropicAdapterOptions): LlmAdapter & { id: string } {
720
785
  const id = opts.id ?? 'anthropic'
786
+ // 1.20-K1(D-48 P2):滚动断点求值身份(每请求经 rollingCacheAllowed 对 env 求值,非构造期定值)
787
+ const rollingIdentity = opts.rollingIdentity ?? id
721
788
  const baseUrl = opts.baseUrl.replace(/\/+$/, '')
722
789
  assertTlsOrLoopback(baseUrl)
723
790
  const url = `${baseUrl}/v1/messages`
@@ -746,7 +813,10 @@ export function createAnthropicAdapter(opts: AnthropicAdapterOptions): LlmAdapte
746
813
  const ref = opts.credentials.resolve(opts.provider ?? id, credResolveOpts)
747
814
  apiKey = opts.credentials.reveal(ref, {})
748
815
  }
749
- return toAnthropic(req, apiKey)
816
+ return toAnthropic(req, apiKey, {
817
+ rolling: rollingCacheAllowed(rollingIdentity),
818
+ rollingBreakpoints: rollingBreakpoints(),
819
+ })
750
820
  },
751
821
  fetchImpl,
752
822
  signal: req.signal,
Binary file
@@ -1 +0,0 @@
1
- {"base64Signature":"MEUCIBP09C8Zaq0IyLxblaFx3lzHcTPG7VbHr9fnhH/1FijbAiEApJUnN0KBsXmmN7YnGoiCSLxATkO1heeQsrlkaPSCh8g=","cert":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUc1akNDQm15Z0F3SUJBZ0lVUlI0MW81WkdEMy9DajBSdkdLMmhzSnNXV0hFd0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpZeE1UQXhNRGswTlRNMFdoY05Nall4TVRBeE1EazFOVE0wV2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVuZXB0SDdzdGxlL2h4ZHRLZ0l6Mm1MdHBmY1lhQVMrb05tWVAKUTM1WSs4aDQwdTRKcmhQeUIzdU56Y050RnpEWlIyRGU5SHlSYktRL1hBallhYTVwaXFPQ0JZc3dnZ1dITUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWZCZ05WSFNNRUdEQVdnQlRmCjArblBWaVFSbHZtbzJPa29WYUxHTGhoa1B6QmlCZ05WSFJFQkFmOEVXREJXaGxSb2RIUndjem92TDJkcGRHaDEKWWk1amIyMHZiSEZqTmpJMEwyTmhjaTF5ZFc1MGFXMWxMeTVuYVhSb2RXSXZkMjl5YTJac2IzZHpMM0psYkdWaApjMlV1ZVcxc1FISmxabk12ZEdGbmN5OHhMakU1TGpBd09RWUtLd1lCQkFHRHZ6QUJBUVFyYUhSMGNITTZMeTkwCmIydGxiaTVoWTNScGIyNXpMbWRwZEdoMVluVnpaWEpqYjI1MFpXNTBMbU52YlRBVkJnb3JCZ0VFQVlPL01BRUMKQkFkeVpXeGxZWE5sTURZR0Npc0dBUVFCZzc4d0FRTUVLR1JqTm1Sak5EQTBZVGN4T0RkaU9UQXpOamRtWldNMwpaRFk1WXpaaVlqSmlPV0V5T1RObVpEZ3dGUVlLS3dZQkJBR0R2ekFCQkFRSGNtVnNaV0Z6WlRBZ0Jnb3JCZ0VFCkFZTy9NQUVGQkJKc2NXTTJNalF2WTJGeUxYSjFiblJwYldVd0hnWUtLd1lCQkFHRHZ6QUJCZ1FRY21WbWN5OTAKWVdkekx6RXVNVGt1TURBN0Jnb3JCZ0VFQVlPL01BRUlCQzBNSzJoMGRIQnpPaTh2ZEc5clpXNHVZV04wYVc5dQpjeTVuYVhSb2RXSjFjMlZ5WTI5dWRHVnVkQzVqYjIwd1pBWUtLd1lCQkFHRHZ6QUJDUVJXREZSb2RIUndjem92CkwyZHBkR2gxWWk1amIyMHZiSEZqTmpJMEwyTmhjaTF5ZFc1MGFXMWxMeTVuYVhSb2RXSXZkMjl5YTJac2IzZHoKTDNKbGJHVmhjMlV1ZVcxc1FISmxabk12ZEdGbmN5OHhMakU1TGpBd09BWUtLd1lCQkFHRHZ6QUJDZ1FxRENoawpZelprWXpRd05HRTNNVGczWWprd016WTNabVZqTjJRMk9XTTJZbUl5WWpsaE1qa3pabVE0TUIwR0Npc0dBUVFCCmc3OHdBUXNFRHd3TloybDBhSFZpTFdodmMzUmxaREExQmdvckJnRUVBWU8vTUFFTUJDY01KV2gwZEhCek9pOHYKWjJsMGFIVmlMbU52YlM5c2NXTTJNalF2WTJGeUxYSjFiblJwYldVd09BWUtLd1lCQkFHRHZ6QUJEUVFxRENoawpZelprWXpRd05HRTNNVGczWWprd016WTNabVZqTjJRMk9XTTJZbUl5WWpsaE1qa3pabVE0TUNBR0Npc0dBUVFCCmc3OHdBUTRFRWd3UWNtVm1jeTkwWVdkekx6RXVNVGt1TURBYUJnb3JCZ0VFQVlPL01BRVBCQXdNQ2pFek5qRXgKT1RJM01UUXdLUVlLS3dZQkJBR0R2ekFCRUFRYkRCbG9kSFJ3Y3pvdkwyZHBkR2gxWWk1amIyMHZiSEZqTmpJMApNQmdHQ2lzR0FRUUJnNzh3QVJFRUNnd0lOamt5TXpRMU5qUXdaQVlLS3dZQkJBR0R2ekFCRWdSV0RGUm9kSFJ3CmN6b3ZMMmRwZEdoMVlpNWpiMjB2YkhGak5qSTBMMk5oY2kxeWRXNTBhVzFsTHk1bmFYUm9kV0l2ZDI5eWEyWnMKYjNkekwzSmxiR1ZoYzJVdWVXMXNRSEpsWm5NdmRHRm5jeTh4TGpFNUxqQXdPQVlLS3dZQkJBR0R2ekFCRXdRcQpEQ2hrWXpaa1l6UXdOR0UzTVRnM1lqa3dNelkzWm1Wak4yUTJPV00yWW1JeVlqbGhNamt6Wm1RNE1CY0dDaXNHCkFRUUJnNzh3QVJRRUNRd0hjbVZzWldGelpUQlpCZ29yQmdFRUFZTy9NQUVWQkVzTVNXaDBkSEJ6T2k4dloybDAKYUhWaUxtTnZiUzlzY1dNMk1qUXZZMkZ5TFhKMWJuUnBiV1V2WVdOMGFXOXVjeTl5ZFc1ekx6UXhNamswTkRnMApOakV3TDJGMGRHVnRjSFJ6THpFd0ZnWUtLd1lCQkFHRHZ6QUJGZ1FJREFad2RXSnNhV013VUFZS0t3WUJCQUdECnZ6QUJHQVJDREVCeVpYQnZPbXh4WXpZeU5FQTJPVEl6TkRVMk5DOWpZWEl0Y25WdWRHbHRaVUF4TXpZeE1Ua3kKTnpFME9uSmxaanB5WldaekwzUmhaM012TVM0eE9TNHdNSUdKQmdvckJnRUVBZFo1QWdRQ0JIc0VlUUIzQUhVQQozVDB3YXNiSEVUSmpHUjRjbVdjM0FxSktYcmplUEszL2g0cHlnQzhwN280QUFBR2hsb0FGM2dBQUJBTUFSakJFCkFpQjVjUGZEb0pVN1EvVTFmdHhtWWlGcyt4ZktQRXQ3ZVBhOWU4MFhaamRYMHdJZ1ZVY3E5UE4ydG03QUpMK08KamdmNVpFNVFvVnFSSW03RzE3Y0ZQUjdva0RVd0NnWUlLb1pJemowRUF3TURhQUF3WlFJeEFPMkNia0VTNmlDTQprUk4wbEZqUkxVVVdwZ1FrMkxQc3FmOTVteVZObDJQLzMvQUtXWTJkUmRnSTlLMThUZ2RGWVFJd1BQQ2VhZlRMCi9nNnpNSTc1bXFLY0ZFVmhqVlJJZnRyMHN0RlRTZkRkT1paR2UrcjFXN2dMUC81alREUTVHZ2ZkCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K","rekorBundle":{"SignedEntryTimestamp":"MEUCIQCOsnCzp5zMWX8CrzrAKjTznpryixWK9TKSOJ2EpPJZ9AIgWycT72YXDKqslN0wfQXBnY1Br76C62gxBCtgx5cF/kg=","Payload":{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI0OWQxOGVhMmEzZDAwMWQzYzY3YzUzYmQ2M2U0MjIzOTNhOTQ0ZmI0ZGVjZTRmNTJjNzA5YzUwYWRlMmM0MGJlIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJQlAwOUM4WmFxMEl5THhibGFGeDNsekhjVFBHN1ZiSHI5Zm5oSC8xRmlqYkFpRUFwSlVuTjBLQnNYbW1ON1luR29pQ1NMeEFUa08xaGVlUXNybGthUFNDaDhnPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVYzFha05EUW0xNVowRjNTVUpCWjBsVlVsSTBNVzgxV2tkRU15OURhakJTZGtkTE1taHpTbk5YVjBoRmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFplRTFVUVhoTlJHc3dUbFJOTUZkb1kwNU5hbGw0VFZSQmVFMUVhekZPVkUwd1YycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZ1WlhCMFNEZHpkR3hsTDJoNFpIUkxaMGw2TW0xTWRIQm1ZMWxoUVZNcmIwNXRXVkFLVVRNMVdTczRhRFF3ZFRSS2NtaFFlVUl6ZFU1NlkwNTBSbnBFV2xJeVJHVTVTSGxTWWt0UkwxaEJhbGxoWVRWd2FYRlBRMEpaYzNkbloxZElUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXWkNaMDVXU0ZOTlJVZEVRVmRuUWxSbUNqQXJibEJXYVZGU2JIWnRiekpQYTI5V1lVeEhUR2hvYTFCNlFtbENaMDVXU0ZKRlFrRm1PRVZYUkVKWGFHeFNiMlJJVW5kamVtOTJUREprY0dSSGFERUtXV2sxYW1JeU1IWmlTRVpxVG1wSk1Fd3lUbWhqYVRGNVpGYzFNR0ZYTVd4TWVUVnVZVmhTYjJSWFNYWmtNamw1WVRKYWMySXpaSHBNTTBwc1lrZFdhQXBqTWxWMVpWY3hjMUZJU214YWJrMTJaRWRHYm1ONU9IaE1ha1UxVEdwQmQwOVJXVXRMZDFsQ1FrRkhSSFo2UVVKQlVWRnlZVWhTTUdOSVRUWk1lVGt3Q21JeWRHeGlhVFZvV1ROU2NHSXlOWHBNYldSd1pFZG9NVmx1Vm5wYVdFcHFZakkxTUZwWE5UQk1iVTUyWWxSQlZrSm5iM0pDWjBWRlFWbFBMMDFCUlVNS1FrRmtlVnBYZUd4WldFNXNUVVJaUjBOcGMwZEJVVkZDWnpjNGQwRlJUVVZMUjFKcVRtMVNhazVFUVRCWlZHTjRUMFJrYVU5VVFYcE9hbVJ0V2xkTk13cGFSRmsxV1hwYWFWbHFTbWxQVjBWNVQxUk9iVnBFWjNkR1VWbExTM2RaUWtKQlIwUjJla0ZDUWtGUlNHTnRWbk5hVjBaNldsUkJaMEpuYjNKQ1owVkZDa0ZaVHk5TlFVVkdRa0pLYzJOWFRUSk5hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFZkMGhuV1V0TGQxbENRa0ZIUkhaNlFVSkNaMUZSWTIxV2JXTjVPVEFLV1Zka2VreDZSWFZOVkd0MVRVUkJOMEpuYjNKQ1owVkZRVmxQTDAxQlJVbENRekJOU3pKb01HUklRbnBQYVRoMlpFYzVjbHBYTkhWWlYwNHdZVmM1ZFFwamVUVnVZVmhTYjJSWFNqRmpNbFo1V1RJNWRXUkhWblZrUXpWcVlqSXdkMXBCV1V0TGQxbENRa0ZIUkhaNlFVSkRVVkpYUkVaU2IyUklVbmRqZW05MkNrd3laSEJrUjJneFdXazFhbUl5TUhaaVNFWnFUbXBKTUV3eVRtaGphVEY1WkZjMU1HRlhNV3hNZVRWdVlWaFNiMlJYU1haa01qbDVZVEphYzJJelpIb0tURE5LYkdKSFZtaGpNbFYxWlZjeGMxRklTbXhhYmsxMlpFZEdibU41T0hoTWFrVTFUR3BCZDA5QldVdExkMWxDUWtGSFJIWjZRVUpEWjFGeFJFTm9hd3BaZWxwcldYcFJkMDVIUlROTlZHY3pXV3ByZDAxNldUTmFiVlpxVGpKUk1rOVhUVEpaYlVsNVdXcHNhRTFxYTNwYWJWRTBUVUl3UjBOcGMwZEJVVkZDQ21jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacFRGZG9kbU16VW14YVJFRXhRbWR2Y2tKblJVVkJXVTh2VFVGRlRVSkRZMDFLVjJnd1pFaENlazlwT0hZS1dqSnNNR0ZJVm1sTWJVNTJZbE01YzJOWFRUSk5hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFZkMDlCV1V0TGQxbENRa0ZIUkhaNlFVSkVVVkZ4UkVOb2F3cFplbHByV1hwUmQwNUhSVE5OVkdjeldXcHJkMDE2V1ROYWJWWnFUakpSTWs5WFRUSlpiVWw1V1dwc2FFMXFhM3BhYlZFMFRVTkJSME5wYzBkQlVWRkNDbWMzT0hkQlVUUkZSV2QzVVdOdFZtMWplVGt3V1Zka2VreDZSWFZOVkd0MVRVUkJZVUpuYjNKQ1owVkZRVmxQTDAxQlJWQkNRWGROUTJwRmVrNXFSWGdLVDFSSk0wMVVVWGRMVVZsTFMzZFpRa0pCUjBSMmVrRkNSVUZSWWtSQ2JHOWtTRkozWTNwdmRrd3laSEJrUjJneFdXazFhbUl5TUhaaVNFWnFUbXBKTUFwTlFtZEhRMmx6UjBGUlVVSm5OemgzUVZKRlJVTm5kMGxPYW10NVRYcFJNVTVxVVhkYVFWbExTM2RaUWtKQlIwUjJla0ZDUldkU1YwUkdVbTlrU0ZKM0NtTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJZa2hHYWs1cVNUQk1NazVvWTJreGVXUlhOVEJoVnpGc1RIazFibUZZVW05a1YwbDJaREk1ZVdFeVduTUtZak5rZWt3elNteGlSMVpvWXpKVmRXVlhNWE5SU0Vwc1dtNU5kbVJIUm01amVUaDRUR3BGTlV4cVFYZFBRVmxMUzNkWlFrSkJSMFIyZWtGQ1JYZFJjUXBFUTJocldYcGFhMWw2VVhkT1IwVXpUVlJuTTFscWEzZE5lbGt6V20xV2FrNHlVVEpQVjAweVdXMUplVmxxYkdoTmFtdDZXbTFSTkUxQ1kwZERhWE5IQ2tGUlVVSm5OemgzUVZKUlJVTlJkMGhqYlZaeldsZEdlbHBVUWxwQ1oyOXlRbWRGUlVGWlR5OU5RVVZXUWtWelRWTlhhREJrU0VKNlQyazRkbG95YkRBS1lVaFdhVXh0VG5aaVV6bHpZMWROTWsxcVVYWlpNa1o1VEZoS01XSnVVbkJpVjFWMldWZE9NR0ZYT1hWamVUbDVaRmMxZWt4NlVYaE5hbXN3VGtSbk1BcE9ha1YzVERKR01HUkhWblJqU0ZKNlRIcEZkMFpuV1V0TGQxbENRa0ZIUkhaNlFVSkdaMUZKUkVGYWQyUlhTbk5oVjAxM1ZVRlpTMHQzV1VKQ1FVZEVDblo2UVVKSFFWSkRSRVZDZVZwWVFuWlBiWGg0V1hwWmVVNUZRVEpQVkVsNlRrUlZNazVET1dwWldFbDBZMjVXZFdSSGJIUmFWVUY0VFhwWmVFMVVhM2tLVG5wRk1FOXVTbXhhYW5CNVdsZGFla3d6VW1oYU0wMTJUVk0wZUU5VE5IZE5TVWRLUW1kdmNrSm5SVVZCWkZvMVFXZFJRMEpJYzBWbFVVSXpRVWhWUVFvelZEQjNZWE5pU0VWVVNtcEhValJqYlZkak0wRnhTa3RZY21wbFVFc3pMMmcwY0hsblF6aHdOMjgwUVVGQlIyaHNiMEZHTTJkQlFVSkJUVUZTYWtKRkNrRnBRalZqVUdaRWIwcFZOMUV2VlRGbWRIaHRXV2xHY3l0NFprdFFSWFEzWlZCaE9XVTRNRmhhYW1SWU1IZEpaMVpWWTNFNVVFNHlkRzAzUVVwTUswOEthbWRtTlZwRk5WRnZWbkZTU1cwM1J6RTNZMFpRVWpkdmEwUlZkME5uV1VsTGIxcEplbW93UlVGM1RVUmhRVUYzV2xGSmVFRlBNa05pYTBWVE5tbERUUXByVWs0d2JFWnFVa3hWVlZkd1oxRnJNa3hRYzNGbU9UVnRlVlpPYkRKUUx6TXZRVXRYV1RKa1VtUm5TVGxMTVRoVVoyUkdXVkZKZDFCUVEyVmhabFJNQ2k5bk5ucE5TVGMxYlhGTFkwWkZWbWhxVmxKSlpuUnlNSE4wUmxSVFprUmtUMXBhUjJVcmNqRlhOMmRNVUM4MWFsUkVVVFZIWjJaa0NpMHRMUzB0UlU1RUlFTkZVbFJKUmtsRFFWUkZMUzB0TFMwSyJ9fX19","integratedTime":1793526337,"logIndex":3625869466,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}}