@lqc123qwe/car-runtime 1.13.0 → 1.14.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.
@@ -0,0 +1,168 @@
1
+ /**
2
+ * 1.14-S2 · 历史压缩面(K-MP-2 / D-40)——策略器 + 执行器 + 确定性摘要
3
+ *
4
+ * 口径(红线先立后码):
5
+ * - **append-only 红线**:压缩 = 追加一条 'compact' 事件(payload 携带摘要 + 折叠前缀边界
6
+ * compactedUpTo),**不改写 / 不删除任何已落链事件**——SessionLog 无 update/delete API,
7
+ * 压缩只走 append;node:test 断言压缩前后 verifyChain() 均 null 且旧事件逐字节不变;
8
+ * - **投影语义**:compact 只影响其后请求的投影(deriveMessages last-wins),压缩前快照
9
+ * (atSeq ≤ compact.seq)投影仍为全量形态——N1 不变量(Model-visible means logged)
10
+ * 逐快照自洽,无需豁免;
11
+ * - **保护段**:首个 system 事件(会话级提示)+ 最近 N turn(protectLastTurns,缺省 4)
12
+ * 不进折叠区间;中途 system 事件按普通事件折叠(内容保留进摘要,信息不丢——D-40 执行期细化);
13
+ * - **工具对完整性(toolPairIntegrity)**:折叠边界若落在 toolCall↔toolResult 配对中间
14
+ * (call 在区间内、result 在保护段)→ 边界回退到该 toolCall(成对整体移出),循环至不动点;
15
+ * - **fail-visible**:无可压缩区间(全部在保护段 / 已压缩过)→ CAR-E-COMPACT-NORANGE 显式报错,
16
+ * 禁静默成功;摘要生成产物非非空字符串 → CAR-E-COMPACT-SUMMARY 显式报错;
17
+ * - **零新依赖**:摘要缺省为确定性生成器(轮次意图 + 工具调用统计 + 收口状态——事实拼装,
18
+ * 禁估算);宿主可注入 LLM 摘要(summarize 缝——内核不依赖 LLM 做压缩);
19
+ * - **触发面**:shouldCompact 判定(thresholdEvents 事实计数,非 token 估算)+ applyCompaction
20
+ * 立即 API;自动挂载点在宿主装配层(runTurn 之间调用,内核不改 runTurn 语义)。
21
+ */
22
+ import type { SessionEvent, SessionLog } from './log.ts'
23
+
24
+ export interface CompactionConfig {
25
+ /** 缺省 false = 行为零变化(1.13 兼容线) */
26
+ enabled?: boolean
27
+ /** 距上次压缩以来的新增事件数阈值(事实计数,非 token 估算);缺省 200 */
28
+ thresholdEvents?: number
29
+ /** 保护最近 N 个 turn 不折叠;缺省 4 */
30
+ protectLastTurns?: number
31
+ }
32
+
33
+ export interface CompactionPlan {
34
+ /** 折叠前缀边界 [0, compactedUpTo)——compact 事件 payload 原样携带 */
35
+ compactedUpTo: number
36
+ /** 折叠区间覆盖的 turn 数(去重) */
37
+ compactedTurns: number
38
+ /** 折叠区间事件引用(供摘要生成;只读) */
39
+ events: readonly SessionEvent[]
40
+ }
41
+
42
+ /** 上次压缩边界(无 compact 事件 = 0)——重复压缩不越界(幂等区间的下界) */
43
+ function lastCompactedUpTo(log: SessionLog): number {
44
+ let last = 0
45
+ for (const e of log.events) {
46
+ if (e.kind !== 'compact') continue
47
+ const p = (e.payload ?? {}) as { compactedUpTo?: unknown }
48
+ if (typeof p.compactedUpTo === 'number' && p.compactedUpTo > last) last = p.compactedUpTo
49
+ }
50
+ return last
51
+ }
52
+
53
+ /** shouldCompact:距上次压缩的新增事件数 ≥ thresholdEvents(enabled 缺省关 = 零变化) */
54
+ export function shouldCompact(log: SessionLog, cfg: CompactionConfig | undefined): boolean {
55
+ if (!cfg || cfg.enabled !== true) return false
56
+ const threshold = cfg.thresholdEvents ?? 200
57
+ return log.events.length - lastCompactedUpTo(log) >= threshold
58
+ }
59
+
60
+ /**
61
+ * planCompaction:计算折叠区间(纯函数,不落链)。
62
+ * 保护段 = 首个 system 事件 + 最近 protectLastTurns 个 turn(按 turnId 首次出现序);
63
+ * 边界回退保证 toolCall↔toolResult 成对(toolPairIntegrity);无可压缩区间显式报错。
64
+ */
65
+ export function planCompaction(log: SessionLog, protectLastTurns = 4): CompactionPlan {
66
+ const events = log.events
67
+ const floor = lastCompactedUpTo(log) // 上界不越过上次压缩边界(幂等)
68
+
69
+ // 保护起点候选:最近 N 个 turn 的首事件 seq
70
+ const turnFirst = new Map<string, number>()
71
+ for (const e of events) {
72
+ if (e.turnId === 'compaction') continue // 压缩事件自身不构成 turn
73
+ if (!turnFirst.has(e.turnId)) turnFirst.set(e.turnId, e.seq)
74
+ }
75
+ const recentTurns = [...turnFirst.entries()].slice(-protectLastTurns)
76
+ let protectStart = recentTurns.length ? Math.min(...recentTurns.map(([, seq]) => seq)) : events.length
77
+
78
+ // 统一规则:折叠区间 = [lowerBound, protectStart),lowerBound = max(上次压缩边界, 首个 system.seq + 1)
79
+ // ——首个 system(会话级提示)恒不被折叠;floor 已含它时(上次压缩边界在其后)不受限
80
+ const firstSystem = events.find(e => e.kind === 'system')
81
+ const lowerBound = firstSystem ? Math.max(floor, firstSystem.seq + 1) : floor
82
+ if (protectStart <= lowerBound) {
83
+ throw new Error(`CAR-E-COMPACT-NORANGE: 无可压缩区间(保护段 = 首个 system + 最近 ${protectLastTurns} turn,上次压缩边界 = ${floor})——禁静默成功`)
84
+ }
85
+
86
+ // toolPairIntegrity:边界落在配对中间 → 回退到 toolCall(成对整体移出),循环至不动点
87
+ for (let guard = 0; guard < 1000; guard++) {
88
+ const callIds = new Map<string, number>() // id → call seq(区间内)
89
+ for (let i = lowerBound; i < protectStart; i++) {
90
+ const e = events[i]!
91
+ if (e.kind === 'toolCall') {
92
+ const id = (e.payload as { id?: unknown } | null)?.id
93
+ if (typeof id === 'string') callIds.set(id, e.seq)
94
+ }
95
+ }
96
+ let earliestCallSeq: number | undefined
97
+ for (let i = protectStart; i < events.length; i++) {
98
+ const e = events[i]!
99
+ if (e.kind !== 'toolResult') continue
100
+ const id = (e.payload as { id?: unknown } | null)?.id
101
+ if (typeof id === 'string' && callIds.has(id)) {
102
+ const callSeq = callIds.get(id)!
103
+ if (earliestCallSeq === undefined || callSeq < earliestCallSeq) earliestCallSeq = callSeq
104
+ }
105
+ }
106
+ if (earliestCallSeq === undefined) break // 无破裂配对——边界成立
107
+ protectStart = earliestCallSeq // 成对整体移出
108
+ if (protectStart <= lowerBound) {
109
+ throw new Error(`CAR-E-COMPACT-NORANGE: 工具对回退后无区间(toolCall↔toolResult 成对完整性不可满足)——禁静默成功`)
110
+ }
111
+ }
112
+
113
+ const compacted = events.slice(lowerBound, protectStart)
114
+ const turns = new Set(compacted.map(e => e.turnId))
115
+ return { compactedUpTo: protectStart, compactedTurns: turns.size, events: compacted }
116
+ }
117
+
118
+ /** 确定性摘要(缺省生成器):事实拼装(轮次意图 + 工具统计 + 收口状态),禁估算禁 LLM 依赖 */
119
+ export function defaultSummarizer(events: readonly SessionEvent[]): string {
120
+ const cut = (v: unknown, n: number) => {
121
+ const s = typeof v === 'string' ? v : JSON.stringify(v)
122
+ return s.length > n ? s.slice(0, n) + '…' : s
123
+ }
124
+ const lines: string[] = [`[会话压缩摘要] 折叠事件 ${events.length} 条(${events[0]?.seq ?? 0}–${events.at(-1)?.seq ?? 0}):`]
125
+ const toolCounts = new Map<string, number>()
126
+ const endReasons = new Map<string, number>()
127
+ for (const e of events) {
128
+ if (e.kind === 'user') lines.push(`- [${e.turnId}] 用户:${cut(e.payload, 200)}`)
129
+ else if (e.kind === 'assistant' && typeof e.payload === 'string' && e.payload.trim()) lines.push(`- [${e.turnId}] 助手:${cut(e.payload, 160)}`)
130
+ else if (e.kind === 'toolCall') {
131
+ const p = e.payload as { tool?: unknown }
132
+ const name = typeof p?.tool === 'string' ? p.tool : '?'
133
+ toolCounts.set(name, (toolCounts.get(name) ?? 0) + 1)
134
+ } else if (e.kind === 'turnEnd') {
135
+ const r = (e.meta as { reason?: unknown } | null)?.reason
136
+ const key = typeof r === 'string' ? r : 'unknown'
137
+ endReasons.set(key, (endReasons.get(key) ?? 0) + 1)
138
+ }
139
+ }
140
+ if (toolCounts.size) {
141
+ lines.push(`工具调用:${[...toolCounts.entries()].map(([t, n]) => `${t} ×${n}`).join('、')}`)
142
+ }
143
+ if (endReasons.size) {
144
+ lines.push(`turn 收口:${[...endReasons.entries()].map(([r, n]) => `${r} ×${n}`).join('、')}`)
145
+ }
146
+ return lines.join('\n')
147
+ }
148
+
149
+ /**
150
+ * applyCompaction:执行压缩(唯一落链动作 = append 一条 'compact' 事件——原事件链不改写)。
151
+ * summarize 缝:宿主可注入 LLM 摘要(返回非空字符串;缺省确定性生成器)。
152
+ */
153
+ export function applyCompaction(
154
+ log: SessionLog,
155
+ opts: { protectLastTurns?: number; turnId?: string; summarize?: (events: readonly SessionEvent[]) => string } = {},
156
+ ): SessionEvent {
157
+ const plan = planCompaction(log, opts.protectLastTurns ?? 4)
158
+ const summary = opts.summarize ? opts.summarize(plan.events) : defaultSummarizer(plan.events)
159
+ if (typeof summary !== 'string' || !summary.trim().length) {
160
+ throw new Error(`CAR-E-COMPACT-SUMMARY: 摘要生成产物为空(fail-visible,禁静默折叠空摘要)`)
161
+ }
162
+ return log.append('runtime', 'compact', opts.turnId ?? 'compaction', {
163
+ summary,
164
+ compactedUpTo: plan.compactedUpTo,
165
+ compactedTurns: plan.compactedTurns,
166
+ compactedEvents: plan.events.length,
167
+ })
168
+ }
@@ -11,7 +11,7 @@ import { createHash } from 'node:crypto'
11
11
  import { readFileSync } from 'node:fs'
12
12
  import { decodeLogBuffer, splitJsonlLines } from './format.ts'
13
13
 
14
- export type EventKind = 'user' | 'assistant' | 'toolCall' | 'toolResult' | 'turnEnd' | 'goalUpdate' | 'hostRaw' | 'fork' | 'system' | 'usage' // M3-S11 加法扩展:多宿主归一化降级通道(pattern 未命中留痕,零静默);M5-DEC4 加法扩展:跨宿主 fork 标记事件;1.4-S2 加法扩展:system prompt 落链(§3.5.5:新模型可见输入 = 新事件类型);1.11-S2 加法扩展:usage 计量事实(D-33,actor='runtime'、model_visible=0 不进模型消息流,deriveMessages 不投影);六值 TurnEndReason 不变
14
+ export type EventKind = 'user' | 'assistant' | 'toolCall' | 'toolResult' | 'turnEnd' | 'goalUpdate' | 'hostRaw' | 'fork' | 'system' | 'usage' | 'compact' | 'routing' // M3-S11 加法扩展:多宿主归一化降级通道(pattern 未命中留痕,零静默);M5-DEC4 加法扩展:跨宿主 fork 标记事件;1.4-S2 加法扩展:system prompt 落链(§3.5.5:新模型可见输入 = 新事件类型);1.11-S2 加法扩展:usage 计量事实(D-33,actor='runtime'、model_visible=0 不进模型消息流,deriveMessages 不投影);六值 TurnEndReason 不变;1.14-S2 加法扩展(K-MP-2 / D-40):历史压缩事件(actor='runtime',append-only 只追加——payload={summary, compactedUpTo, ...},投影按 compactedUpTo 前缀折叠为摘要,原事件链不改写);1.14-S4 加法扩展(K-POOL-1 / D-42):模型池路由决策(actor='runtime',payload.type='decide',selected/reason/retries/attempts——禁静默换模型;model_visible=0 不进模型消息流,deriveMessages 不投影——同 usage 纪律)
15
15
  export type Actor = 'user' | 'model' | 'plugin' | 'runtime'
16
16
  export interface SessionEvent {
17
17
  seq: number
@@ -91,17 +91,33 @@ export class SessionLog {
91
91
  * 模型可见流只保留最终结果(真实 provider 对重复 tool_call_id/tool_use_id 拒绝或未定义,
92
92
  * 1.4 观察项经真实适配器落地升级为缺陷面)。id 缺席不收敛(hostRaw/无 id 载荷形态不变,
93
93
  * golden ④ 投影语义兼容);授权拒绝单条即唯一结果原样保留。适配器侧零改动(消费已收敛上游)。
94
+ * 1.14-S2(K-MP-2 / D-40):compact 事件投影——scoped 内最后一条 compact 生效(last-wins),
95
+ * 其 compactedUpTo 前缀折叠为单条 system 摘要(事件原文不改写,仅投影层折叠);compact 之前的
96
+ * 请求快照(atSeq ≤ compact.seq)不含 compact 事件 → 投影仍为全量形态(N1 不变量自洽——
97
+ * 「请求时发出 = 日志前缀投影」逐快照成立);无 compact 事件的日志投影与 1.13 逐字节一致。
94
98
  */
95
99
  deriveMessages(upTo?: number): Array<{ role: string; [k: string]: unknown }> {
96
100
  const scoped = upTo !== undefined ? this.events.slice(0, upTo) : this.events
101
+ let compactEvent: SessionEvent | undefined
102
+ for (const e of scoped) { if (e.kind === 'compact') compactEvent = e } // last-wins
103
+ let compactedUpTo: number | undefined
104
+ if (compactEvent) {
105
+ const cp = (compactEvent.payload ?? {}) as { summary?: unknown; compactedUpTo?: unknown }
106
+ if (typeof cp.summary !== 'string' || !cp.summary.length || typeof cp.compactedUpTo !== 'number') {
107
+ throw new Error(`CAR-E-COMPACT-PAYLOAD: compact 事件 seq=${compactEvent.seq} 缺有效 summary/compactedUpTo(投影拒绝——fail-visible)`)
108
+ }
109
+ compactedUpTo = cp.compactedUpTo // 折叠边界 = payload 声明的前缀边界(非 compact 事件自身 seq)
110
+ }
111
+ const projected = compactedUpTo !== undefined ? scoped.filter(e => e.seq >= compactedUpTo!) : scoped
97
112
  const lastSeqById = new Map<string, number>()
98
- for (const e of scoped) {
113
+ for (const e of projected) {
99
114
  if (e.kind !== 'toolResult') continue
100
115
  const id = (e.payload as { id?: unknown } | null)?.id
101
116
  if (typeof id === 'string') lastSeqById.set(id, e.seq)
102
117
  }
103
118
  const out: Array<{ role: string; [k: string]: unknown }> = []
104
- for (const e of scoped) {
119
+ if (compactEvent) out.push({ role: 'system', content: ((compactEvent.payload as { summary: string }).summary) })
120
+ for (const e of projected) {
105
121
  if (e.kind === 'system') out.push({ role: 'system', content: e.payload })
106
122
  else if (e.kind === 'user') out.push({ role: 'user', content: e.payload })
107
123
  else if (e.kind === 'assistant') out.push({ role: 'assistant', content: e.payload })
Binary file
@@ -1 +0,0 @@
1
- {"base64Signature":"MEYCIQDu5NPROyK/7sliW5k03dzCfSCVM3HhUYFPkWDY6KvMCAIhAIG8ji1zQSA6IOMN8CN7t7dhhnfqNbu392w54ymS7VCO","cert":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUc2RENDQm0yZ0F3SUJBZ0lVVHdxNVo4Vi9zK2VZZHZVR3ZnRzNSNzVPejhJd0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpZeE1ERXhNRGMwTnpBM1doY05Nall4TURFeE1EYzFOekEzV2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUV0b0Nld1FoR2IyaHUrM29vYU1KVmJpQ2xwaTBnOXQ5MXZiM3kKbkxzTUZUekJLQnQwalRkcFhEa2JONVVTWHRSRkVyVmNEVGdPYmFpVGRXbEVobldDU2FPQ0JZd3dnZ1dJTUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWZCZ05WSFNNRUdEQVdnQlRmCjArblBWaVFSbHZtbzJPa29WYUxHTGhoa1B6QmlCZ05WSFJFQkFmOEVXREJXaGxSb2RIUndjem92TDJkcGRHaDEKWWk1amIyMHZiSEZqTmpJMEwyTmhjaTF5ZFc1MGFXMWxMeTVuYVhSb2RXSXZkMjl5YTJac2IzZHpMM0psYkdWaApjMlV1ZVcxc1FISmxabk12ZEdGbmN5OHhMakV6TGpBd09RWUtLd1lCQkFHRHZ6QUJBUVFyYUhSMGNITTZMeTkwCmIydGxiaTVoWTNScGIyNXpMbWRwZEdoMVluVnpaWEpqYjI1MFpXNTBMbU52YlRBVkJnb3JCZ0VFQVlPL01BRUMKQkFkeVpXeGxZWE5sTURZR0Npc0dBUVFCZzc4d0FRTUVLR0ZpTlRrME5qZzJNVE14WlRWaVl6ZGlNams1WTJGawpNRGxqWVRJNVlURmxOV1JoT1dWbFptTXdGUVlLS3dZQkJBR0R2ekFCQkFRSGNtVnNaV0Z6WlRBZ0Jnb3JCZ0VFCkFZTy9NQUVGQkJKc2NXTTJNalF2WTJGeUxYSjFiblJwYldVd0hnWUtLd1lCQkFHRHZ6QUJCZ1FRY21WbWN5OTAKWVdkekx6RXVNVE11TURBN0Jnb3JCZ0VFQVlPL01BRUlCQzBNSzJoMGRIQnpPaTh2ZEc5clpXNHVZV04wYVc5dQpjeTVuYVhSb2RXSjFjMlZ5WTI5dWRHVnVkQzVqYjIwd1pBWUtLd1lCQkFHRHZ6QUJDUVJXREZSb2RIUndjem92CkwyZHBkR2gxWWk1amIyMHZiSEZqTmpJMEwyTmhjaTF5ZFc1MGFXMWxMeTVuYVhSb2RXSXZkMjl5YTJac2IzZHoKTDNKbGJHVmhjMlV1ZVcxc1FISmxabk12ZEdGbmN5OHhMakV6TGpBd09BWUtLd1lCQkFHRHZ6QUJDZ1FxRENoaApZalU1TkRZNE5qRXpNV1UxWW1NM1lqSTVPV05oWkRBNVkyRXlPV0V4WlRWa1lUbGxaV1pqTUIwR0Npc0dBUVFCCmc3OHdBUXNFRHd3TloybDBhSFZpTFdodmMzUmxaREExQmdvckJnRUVBWU8vTUFFTUJDY01KV2gwZEhCek9pOHYKWjJsMGFIVmlMbU52YlM5c2NXTTJNalF2WTJGeUxYSjFiblJwYldVd09BWUtLd1lCQkFHRHZ6QUJEUVFxRENoaApZalU1TkRZNE5qRXpNV1UxWW1NM1lqSTVPV05oWkRBNVkyRXlPV0V4WlRWa1lUbGxaV1pqTUNBR0Npc0dBUVFCCmc3OHdBUTRFRWd3UWNtVm1jeTkwWVdkekx6RXVNVE11TURBYUJnb3JCZ0VFQVlPL01BRVBCQXdNQ2pFek5qRXgKT1RJM01UUXdLUVlLS3dZQkJBR0R2ekFCRUFRYkRCbG9kSFJ3Y3pvdkwyZHBkR2gxWWk1amIyMHZiSEZqTmpJMApNQmdHQ2lzR0FRUUJnNzh3QVJFRUNnd0lOamt5TXpRMU5qUXdaQVlLS3dZQkJBR0R2ekFCRWdSV0RGUm9kSFJ3CmN6b3ZMMmRwZEdoMVlpNWpiMjB2YkhGak5qSTBMMk5oY2kxeWRXNTBhVzFsTHk1bmFYUm9kV0l2ZDI5eWEyWnMKYjNkekwzSmxiR1ZoYzJVdWVXMXNRSEpsWm5NdmRHRm5jeTh4TGpFekxqQXdPQVlLS3dZQkJBR0R2ekFCRXdRcQpEQ2hoWWpVNU5EWTROakV6TVdVMVltTTNZakk1T1dOaFpEQTVZMkV5T1dFeFpUVmtZVGxsWldaak1CY0dDaXNHCkFRUUJnNzh3QVJRRUNRd0hjbVZzWldGelpUQlpCZ29yQmdFRUFZTy9NQUVWQkVzTVNXaDBkSEJ6T2k4dloybDAKYUhWaUxtTnZiUzlzY1dNMk1qUXZZMkZ5TFhKMWJuUnBiV1V2WVdOMGFXOXVjeTl5ZFc1ekx6TTRNVEl5TlRNdwpPVFUxTDJGMGRHVnRjSFJ6THpFd0ZnWUtLd1lCQkFHRHZ6QUJGZ1FJREFad2RXSnNhV013VUFZS0t3WUJCQUdECnZ6QUJHQVJDREVCeVpYQnZPbXh4WXpZeU5FQTJPVEl6TkRVMk5DOWpZWEl0Y25WdWRHbHRaVUF4TXpZeE1Ua3kKTnpFME9uSmxaanB5WldaekwzUmhaM012TVM0eE15NHdNSUdLQmdvckJnRUVBZFo1QWdRQ0JId0VlZ0I0QUhZQQozVDB3YXNiSEVUSmpHUjRjbVdjM0FxSktYcmplUEszL2g0cHlnQzhwN280QUFBR2hLZTRIOVFBQUJBTUFSekJGCkFpRUF3TnZLcjBnRkxqN1UyZ291bW43Y0N5WEVwY1FmYzczYXQ1UG1yQ21xNW0wQ0lDaVBCendPbG1yb0E0RHAKRE5DWktGRlV6dHdTVmhlV1g5cEEvMVgrK3lReU1Bb0dDQ3FHU000OUJBTURBMmtBTUdZQ01RQ0MrblJlNmNsUwptU3gvVDMrR0hyQk5GMXpiKzlpQnk1MmVLYTdiV1hYZU9zeUo5M2FUNllkTkFma08vcVpYeVVvQ01RQ3pJeFNyClI1R2pQNE9MaDB6Zk03OXlPUlVuZ2pNU1JxdlJ5V09LdUNUSDMxd1FXditwdHJMcUlPdE9ma0hQZFowPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==","rekorBundle":{"SignedEntryTimestamp":"MEUCIBWwDsvDPdrIDJmBsIHEQz0vK/SOKhcLFQVQeGzh07Z6AiEAq+28G2Eign0kGlCUHuKJRRQvcEC9K/fsOQWVP5HnXN0=","Payload":{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIzZDRmODY5YjI1MWU0OGM3MmMxZmUyMjRkNTQ5ZDFhOWUzN2YzMGRjYjE2ZGI5Nzc1MzI1NzkxZjAyYjBkOTc0In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FWUNJUUR1NU5QUk95Sy83c2xpVzVrMDNkekNmU0NWTTNIaFVZRlBrV0RZNkt2TUNBSWhBSUc4amkxelFTQTZJT01OOENON3Q3ZGhobmZxTmJ1MzkydzU0eW1TN1ZDTyIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVYzJSRU5EUW0weVowRjNTVUpCWjBsVlZIZHhOVm80Vmk5eksyVlpaSFpWUjNablJ6TlNOelZQZWpoSmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFplRTFFUlhoTlJHTXdUbnBCTTFkb1kwNU5hbGw0VFVSRmVFMUVZekZPZWtFelYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVYwYjBObGQxRm9SMkl5YUhVck0yOXZZVTFLVm1KcFEyeHdhVEJuT1hRNU1YWmlNM2tLYmt4elRVWlVla0pMUW5Rd2FsUmtjRmhFYTJKT05WVlRXSFJTUmtWeVZtTkVWR2RQWW1GcFZHUlhiRVZvYmxkRFUyRlBRMEpaZDNkbloxZEpUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXWkNaMDVXU0ZOTlJVZEVRVmRuUWxSbUNqQXJibEJXYVZGU2JIWnRiekpQYTI5V1lVeEhUR2hvYTFCNlFtbENaMDVXU0ZKRlFrRm1PRVZYUkVKWGFHeFNiMlJJVW5kamVtOTJUREprY0dSSGFERUtXV2sxYW1JeU1IWmlTRVpxVG1wSk1Fd3lUbWhqYVRGNVpGYzFNR0ZYTVd4TWVUVnVZVmhTYjJSWFNYWmtNamw1WVRKYWMySXpaSHBNTTBwc1lrZFdhQXBqTWxWMVpWY3hjMUZJU214YWJrMTJaRWRHYm1ONU9IaE1ha1Y2VEdwQmQwOVJXVXRMZDFsQ1FrRkhSSFo2UVVKQlVWRnlZVWhTTUdOSVRUWk1lVGt3Q21JeWRHeGlhVFZvV1ROU2NHSXlOWHBNYldSd1pFZG9NVmx1Vm5wYVdFcHFZakkxTUZwWE5UQk1iVTUyWWxSQlZrSm5iM0pDWjBWRlFWbFBMMDFCUlVNS1FrRmtlVnBYZUd4WldFNXNUVVJaUjBOcGMwZEJVVkZDWnpjNGQwRlJUVVZMUjBacFRsUnJNRTVxWnpKTlZFMTRXbFJXYVZsNlpHbE5hbXMxV1RKR2F3cE5SR3hxV1ZSSk5WbFVSbXhPVjFKb1QxZFdiRnB0VFhkR1VWbExTM2RaUWtKQlIwUjJla0ZDUWtGUlNHTnRWbk5hVjBaNldsUkJaMEpuYjNKQ1owVkZDa0ZaVHk5TlFVVkdRa0pLYzJOWFRUSk5hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFZkMGhuV1V0TGQxbENRa0ZIUkhaNlFVSkNaMUZSWTIxV2JXTjVPVEFLV1Zka2VreDZSWFZOVkUxMVRVUkJOMEpuYjNKQ1owVkZRVmxQTDAxQlJVbENRekJOU3pKb01HUklRbnBQYVRoMlpFYzVjbHBYTkhWWlYwNHdZVmM1ZFFwamVUVnVZVmhTYjJSWFNqRmpNbFo1V1RJNWRXUkhWblZrUXpWcVlqSXdkMXBCV1V0TGQxbENRa0ZIUkhaNlFVSkRVVkpYUkVaU2IyUklVbmRqZW05MkNrd3laSEJrUjJneFdXazFhbUl5TUhaaVNFWnFUbXBKTUV3eVRtaGphVEY1WkZjMU1HRlhNV3hNZVRWdVlWaFNiMlJYU1haa01qbDVZVEphYzJJelpIb0tURE5LYkdKSFZtaGpNbFYxWlZjeGMxRklTbXhhYmsxMlpFZEdibU41T0hoTWFrVjZUR3BCZDA5QldVdExkMWxDUWtGSFJIWjZRVUpEWjFGeFJFTm9hQXBaYWxVMVRrUlpORTVxUlhwTlYxVXhXVzFOTTFscVNUVlBWMDVvV2tSQk5Wa3lSWGxQVjBWNFdsUldhMWxVYkd4YVYxcHFUVUl3UjBOcGMwZEJVVkZDQ21jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacFRGZG9kbU16VW14YVJFRXhRbWR2Y2tKblJVVkJXVTh2VFVGRlRVSkRZMDFLVjJnd1pFaENlazlwT0hZS1dqSnNNR0ZJVm1sTWJVNTJZbE01YzJOWFRUSk5hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFZkMDlCV1V0TGQxbENRa0ZIUkhaNlFVSkVVVkZ4UkVOb2FBcFphbFUxVGtSWk5FNXFSWHBOVjFVeFdXMU5NMWxxU1RWUFYwNW9Xa1JCTlZreVJYbFBWMFY0V2xSV2ExbFViR3hhVjFwcVRVTkJSME5wYzBkQlVWRkNDbWMzT0hkQlVUUkZSV2QzVVdOdFZtMWplVGt3V1Zka2VreDZSWFZOVkUxMVRVUkJZVUpuYjNKQ1owVkZRVmxQTDAxQlJWQkNRWGROUTJwRmVrNXFSWGdLVDFSSk0wMVVVWGRMVVZsTFMzZFpRa0pCUjBSMmVrRkNSVUZSWWtSQ2JHOWtTRkozWTNwdmRrd3laSEJrUjJneFdXazFhbUl5TUhaaVNFWnFUbXBKTUFwTlFtZEhRMmx6UjBGUlVVSm5OemgzUVZKRlJVTm5kMGxPYW10NVRYcFJNVTVxVVhkYVFWbExTM2RaUWtKQlIwUjJla0ZDUldkU1YwUkdVbTlrU0ZKM0NtTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJZa2hHYWs1cVNUQk1NazVvWTJreGVXUlhOVEJoVnpGc1RIazFibUZZVW05a1YwbDJaREk1ZVdFeVduTUtZak5rZWt3elNteGlSMVpvWXpKVmRXVlhNWE5SU0Vwc1dtNU5kbVJIUm01amVUaDRUR3BGZWt4cVFYZFBRVmxMUzNkWlFrSkJSMFIyZWtGQ1JYZFJjUXBFUTJob1dXcFZOVTVFV1RST2FrVjZUVmRWTVZsdFRUTlpha2sxVDFkT2FGcEVRVFZaTWtWNVQxZEZlRnBVVm10WlZHeHNXbGRhYWsxQ1kwZERhWE5IQ2tGUlVVSm5OemgzUVZKUlJVTlJkMGhqYlZaeldsZEdlbHBVUWxwQ1oyOXlRbWRGUlVGWlR5OU5RVVZXUWtWelRWTlhhREJrU0VKNlQyazRkbG95YkRBS1lVaFdhVXh0VG5aaVV6bHpZMWROTWsxcVVYWlpNa1o1VEZoS01XSnVVbkJpVjFWMldWZE9NR0ZYT1hWamVUbDVaRmMxZWt4NlRUUk5WRWw1VGxSTmR3cFBWRlV4VERKR01HUkhWblJqU0ZKNlRIcEZkMFpuV1V0TGQxbENRa0ZIUkhaNlFVSkdaMUZKUkVGYWQyUlhTbk5oVjAxM1ZVRlpTMHQzV1VKQ1FVZEVDblo2UVVKSFFWSkRSRVZDZVZwWVFuWlBiWGg0V1hwWmVVNUZRVEpQVkVsNlRrUlZNazVET1dwWldFbDBZMjVXZFdSSGJIUmFWVUY0VFhwWmVFMVVhM2tLVG5wRk1FOXVTbXhhYW5CNVdsZGFla3d6VW1oYU0wMTJUVk0wZUUxNU5IZE5TVWRMUW1kdmNrSm5SVVZCWkZvMVFXZFJRMEpJZDBWbFowSTBRVWhaUVFvelZEQjNZWE5pU0VWVVNtcEhValJqYlZkak0wRnhTa3RZY21wbFVFc3pMMmcwY0hsblF6aHdOMjgwUVVGQlIyaExaVFJJT1ZGQlFVSkJUVUZTZWtKR0NrRnBSVUYzVG5aTGNqQm5Sa3hxTjFVeVoyOTFiVzQzWTBONVdFVndZMUZtWXpjellYUTFVRzF5UTIxeE5XMHdRMGxEYVZCQ2VuZFBiRzF5YjBFMFJIQUtSRTVEV2t0R1JsVjZkSGRUVm1obFYxZzVjRUV2TVZnckszbFJlVTFCYjBkRFEzRkhVMDAwT1VKQlRVUkJNbXRCVFVkWlEwMVJRME1yYmxKbE5tTnNVd3B0VTNndlZETXJSMGh5UWs1R01YcGlLemxwUW5rMU1tVkxZVGRpVjFoWVpVOXplVW81TTJGVU5sbGtUa0ZtYTA4dmNWcFllVlZ2UTAxUlEzcEplRk55Q2xJMVIycFFORTlNYURCNlprMDNPWGxQVWxWdVoycE5VMUp4ZGxKNVYwOUxkVU5VU0RNeGQxRlhkaXR3ZEhKTWNVbFBkRTltYTBoUVpGb3dQUW90TFMwdExVVk9SQ0JEUlZKVVNVWkpRMEZVUlMwdExTMHRDZz09In19fX0=","integratedTime":1791704828,"logIndex":3203126009,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}}