@lqc123qwe/car-runtime 1.12.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.
- package/README.md +10 -1
- package/dist/SHA256SUMS +1 -1
- package/dist/authz/authz.d.ts +9 -0
- package/dist/authz/authz.js +15 -0
- package/dist/car-runtime-1.14.0.tgz +0 -0
- package/dist/car-runtime-1.14.0.tgz.sig.bundle +1 -0
- package/dist/cli.js +48 -16
- package/dist/load/config.d.ts +45 -0
- package/dist/load/config.js +84 -1
- package/dist/load/pool.d.ts +97 -0
- package/dist/load/pool.js +261 -0
- package/dist/loop/stop.d.ts +6 -0
- package/dist/loop/stop.js +14 -3
- package/dist/release-audit.jsonl +4 -4
- package/dist/runtime-core/chatStep.js +2 -0
- package/dist/runtime-core/credentials.d.ts +2 -0
- package/dist/runtime-core/credentials.js +2 -1
- package/dist/runtime-core/llm.d.ts +6 -0
- package/dist/runtime-core/llm.js +54 -3
- package/dist/runtime-core/types.d.ts +15 -0
- package/dist/sandbox/sandbox.d.ts +7 -1
- package/dist/sandbox/sandbox.js +10 -4
- package/dist/sbom.json +24 -14
- package/dist/seam.d.ts +15 -1
- package/dist/seam.js +17 -1
- package/dist/session/compact.d.ts +57 -0
- package/dist/session/compact.js +128 -0
- package/dist/session/log.d.ts +5 -1
- package/dist/session/log.js +22 -2
- package/package.json +1 -1
- package/src/authz/authz.ts +16 -0
- package/src/cli.ts +48 -17
- package/src/load/config.ts +135 -1
- package/src/load/pool.ts +339 -0
- package/src/loop/stop.ts +19 -4
- package/src/runtime-core/chatStep.ts +2 -0
- package/src/runtime-core/credentials.ts +4 -1
- package/src/runtime-core/llm.ts +59 -3
- package/src/runtime-core/types.ts +11 -0
- package/src/sandbox/sandbox.ts +20 -5
- package/src/seam.ts +17 -1
- package/src/session/compact.ts +168 -0
- package/src/session/log.ts +19 -3
- package/dist/car-runtime-1.12.0.tgz +0 -0
- package/dist/car-runtime-1.12.0.tgz.sig.bundle +0 -1
package/src/runtime-core/llm.ts
CHANGED
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
* 解析「先摘 evt.usage 再判 choice」(空 choices 尾包不再被吞);anthropic message_start(input 侧)
|
|
26
26
|
* + message_delta(output 侧)两包合并(message_delta 缺席 = output 不完整,整条不落);
|
|
27
27
|
* 归一口径 §1.1(openai 减法 / anthropic 透传 + cache_creation),畸形/缺失整条不落(禁补造)。
|
|
28
|
+
* - 1.14-S1(K-MP-1 / D-39):思考参数透传——LlmRequest.reasoning.effort 按家族映射:
|
|
29
|
+
* openai-compat = `reasoning_effort`;anthropic = `thinking:{type:'enabled',budget_tokens}`
|
|
30
|
+
* (ANTHROPIC_THINKING_BUDGET 表);**off / 缺席 = 字节面不下发**;未知 effort 值显式报错
|
|
31
|
+
* (CAR-E-LLM-REASONING,fail-visible 禁静默忽略——宿主越型传入防线);anthropic 侧
|
|
32
|
+
* budget ≥ max_tokens 违反 API 约束 → 显式报错提示调高(禁静默改写 max_tokens)。
|
|
28
33
|
*/
|
|
29
34
|
import { CarM8Error, providerUnreachable } from './errors.ts'
|
|
30
35
|
import { CredentialService } from './credentials.ts'
|
|
@@ -72,6 +77,22 @@ export class AdapterRegistry {
|
|
|
72
77
|
}
|
|
73
78
|
}
|
|
74
79
|
|
|
80
|
+
// ==================== 1.14-S1(K-MP-1 / D-39)思考参数映射 ====================
|
|
81
|
+
|
|
82
|
+
/** 归一思考档位:缺席/off → undefined(不下发);low/medium/high 原样;未知值 fail-visible(宿主越型传入防线) */
|
|
83
|
+
export function normalizeReasoningEffort(effort: unknown): 'off' | 'low' | 'medium' | 'high' | undefined {
|
|
84
|
+
if (effort === undefined || effort === null) return undefined
|
|
85
|
+
if (effort === 'off' || effort === 'low' || effort === 'medium' || effort === 'high') return effort
|
|
86
|
+
throw new Error(`CAR-E-LLM-REASONING: 未知思考档位 "${String(effort)}"(合法:off/low/medium/high;off/缺席 = 不下发)`)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** anthropic effort → thinking.budget_tokens 映射(D-39 登记表);off/缺席不下发 */
|
|
90
|
+
const ANTHROPIC_THINKING_BUDGET: Record<'low' | 'medium' | 'high', number> = {
|
|
91
|
+
low: 2048,
|
|
92
|
+
medium: 8192,
|
|
93
|
+
high: 16384,
|
|
94
|
+
}
|
|
95
|
+
|
|
75
96
|
// ==================== finishReason 不可变守卫(SDK 边界) ====================
|
|
76
97
|
|
|
77
98
|
/** 首个 finishReason 定格;适配器后续给出不同值 = 契约违规显式抛错(不改写不吞没) */
|
|
@@ -264,6 +285,8 @@ export interface OpenAICompatOptions {
|
|
|
264
285
|
/** 凭据服务与 provider 名(Bearer token 来源) */
|
|
265
286
|
credentials?: CredentialService
|
|
266
287
|
provider?: string
|
|
288
|
+
/** 1.14-S4(K-POOL-1):成员级凭据 env 变量名引用(modelPool keyEnv——resolve 精确候选;keychain 链保持在前) */
|
|
289
|
+
credentialEnvVars?: string[]
|
|
267
290
|
fetchImpl?: typeof fetch
|
|
268
291
|
/** §3.5.4 基线:总 120s / 首字节 30s / 2 次退避 1s-2s(测试可注入缩短) */
|
|
269
292
|
timeoutMs?: number
|
|
@@ -288,6 +311,9 @@ class Retryable extends Error {}
|
|
|
288
311
|
* 1.11-S1(D-33 / 计量设计 §1.1):openai usage 归一——prompt_tokens 含 cached,inputTokens
|
|
289
312
|
* 必须做减法(prompt − prompt_tokens_details.cached_tokens);cache_creation 恒 0(无对应字段,
|
|
290
313
|
* 登记不估算)。usage 对象缺席/畸形(两侧主字段任一非数值)= 整条不落(缺失口径,禁补造)。
|
|
314
|
+
* 1.14-S3(K-MP-2b / D-41):分段计量——completion_tokens_details.reasoning_tokens(思考输出
|
|
315
|
+
* 细分,provider 真实事实)数值才采(segments.reasoningTokens);details/字段缺席或非数值 =
|
|
316
|
+
* segments 不带键(禁估算补造)。
|
|
291
317
|
*/
|
|
292
318
|
function normalizeOpenAIUsage(u: unknown, model: string, adapterId: string): LlmUsage | undefined {
|
|
293
319
|
if (!u || typeof u !== 'object') return undefined
|
|
@@ -295,6 +321,8 @@ function normalizeOpenAIUsage(u: unknown, model: string, adapterId: string): Llm
|
|
|
295
321
|
if (typeof r.prompt_tokens !== 'number' || typeof r.completion_tokens !== 'number') return undefined
|
|
296
322
|
const details = (r.prompt_tokens_details ?? null) as Record<string, unknown> | null
|
|
297
323
|
const cached = details && typeof details.cached_tokens === 'number' ? details.cached_tokens : 0
|
|
324
|
+
const compDetails = (r.completion_tokens_details ?? null) as Record<string, unknown> | null
|
|
325
|
+
const reasoning = compDetails && typeof compDetails.reasoning_tokens === 'number' ? compDetails.reasoning_tokens : undefined
|
|
298
326
|
return {
|
|
299
327
|
inputTokens: r.prompt_tokens - cached,
|
|
300
328
|
outputTokens: r.completion_tokens,
|
|
@@ -303,6 +331,7 @@ function normalizeOpenAIUsage(u: unknown, model: string, adapterId: string): Llm
|
|
|
303
331
|
model,
|
|
304
332
|
adapterId,
|
|
305
333
|
providerRaw: u,
|
|
334
|
+
...(reasoning !== undefined ? { segments: { reasoningTokens: reasoning } } : {}),
|
|
306
335
|
}
|
|
307
336
|
}
|
|
308
337
|
|
|
@@ -394,6 +423,10 @@ export function createOpenAICompatAdapter(opts: OpenAICompatOptions): LlmAdapter
|
|
|
394
423
|
const firstByteMs = opts.firstByteMs ?? 30_000
|
|
395
424
|
const retries = opts.retries ?? 2
|
|
396
425
|
const backoffMs = opts.backoffMs ?? [1_000, 2_000]
|
|
426
|
+
// 1.14-S4(K-POOL-1):成员级 keyEnv 精确候选——keyEnv 引用即显式意见(env 通道开启;候选=仅该变量,keychain 链保持在前)
|
|
427
|
+
const credResolveOpts = opts.credentialEnvVars?.length
|
|
428
|
+
? { envVarCandidates: opts.credentialEnvVars, allowEnvFallback: true }
|
|
429
|
+
: {}
|
|
397
430
|
|
|
398
431
|
const toOpenAI = (req: LlmRequest, apiKey: string) => ({
|
|
399
432
|
// 1.4-BUG-1:method: 'POST' 缺失——M8 最小实现潜伏缺陷(注入式 fetch 的单测忽略 method,
|
|
@@ -406,6 +439,11 @@ export function createOpenAICompatAdapter(opts: OpenAICompatOptions): LlmAdapter
|
|
|
406
439
|
body: JSON.stringify({
|
|
407
440
|
model: req.model,
|
|
408
441
|
stream: true,
|
|
442
|
+
// 1.14-S1(K-MP-1 / D-39):思考参数——effort 非 off/缺席才下发 reasoning_effort(off = 字节面零痕迹)
|
|
443
|
+
...(normalizeReasoningEffort(req.reasoning?.effort) !== undefined &&
|
|
444
|
+
normalizeReasoningEffort(req.reasoning?.effort) !== 'off'
|
|
445
|
+
? { reasoning_effort: normalizeReasoningEffort(req.reasoning?.effort) }
|
|
446
|
+
: {}),
|
|
409
447
|
// 1.11-S1(D-33 / 计量设计 §9-2 推荐A):usage 采集默认开启;第三方网关不兼容时的
|
|
410
448
|
// 回退方案(baseUrl 白名单)登记可回退项——不支持网关走缺失口径(禁估算补造)
|
|
411
449
|
stream_options: { include_usage: true },
|
|
@@ -439,7 +477,7 @@ export function createOpenAICompatAdapter(opts: OpenAICompatOptions): LlmAdapter
|
|
|
439
477
|
buildRequest: async () => {
|
|
440
478
|
let apiKey = ''
|
|
441
479
|
if (opts.credentials) {
|
|
442
|
-
const ref = opts.credentials.resolve(opts.provider ?? id,
|
|
480
|
+
const ref = opts.credentials.resolve(opts.provider ?? id, credResolveOpts)
|
|
443
481
|
apiKey = opts.credentials.reveal(ref, {})
|
|
444
482
|
}
|
|
445
483
|
return toOpenAI(req, apiKey)
|
|
@@ -470,6 +508,8 @@ export interface AnthropicAdapterOptions {
|
|
|
470
508
|
sleep?: (ms: number) => Promise<void>
|
|
471
509
|
/** D-20:Anthropic API max_tokens 必填——req.maxTokens 缺席时取此缺省(provider 差异登记,缺省 4096) */
|
|
472
510
|
defaultMaxTokens?: number
|
|
511
|
+
/** 1.14-S4(K-POOL-1):成员级凭据 env 变量名引用(modelPool keyEnv——resolve 精确候选;keychain 链保持在前) */
|
|
512
|
+
credentialEnvVars?: string[]
|
|
473
513
|
}
|
|
474
514
|
|
|
475
515
|
/** stop_reason → CAR finishReason(未映射 fail-visible——openai FINISH_MAP 同纪律) */
|
|
@@ -495,6 +535,8 @@ function mergeAnthropicUsage(
|
|
|
495
535
|
): LlmUsage | undefined {
|
|
496
536
|
if (!start || !delta) return undefined
|
|
497
537
|
if (typeof start.input_tokens !== 'number' || typeof delta.output_tokens !== 'number') return undefined
|
|
538
|
+
// 1.14-S3(K-MP-2b / D-41):anthropic 无输出细分事实(thinking 计入 output_tokens)→
|
|
539
|
+
// segments 不带键(禁估算补造——桌面色条退化单色,登记口径)
|
|
498
540
|
return {
|
|
499
541
|
inputTokens: start.input_tokens,
|
|
500
542
|
outputTokens: delta.output_tokens,
|
|
@@ -545,6 +587,15 @@ function toAnthropic(req: LlmRequest, apiKey: string) {
|
|
|
545
587
|
}
|
|
546
588
|
}
|
|
547
589
|
flushToolResults()
|
|
590
|
+
// 1.14-S1(K-MP-1 / D-39):思考参数——effort → thinking.budget_tokens(ANTHROPIC_THINKING_BUDGET 表);
|
|
591
|
+
// off/缺席不下发;budget ≥ max_tokens 违反 Messages API 约束(max_tokens 必须大于 budget_tokens)→
|
|
592
|
+
// 显式报错提示调高(fail-visible,禁静默改写 max_tokens)
|
|
593
|
+
const reasoningEffort = normalizeReasoningEffort(req.reasoning?.effort)
|
|
594
|
+
const thinkingBudget = reasoningEffort && reasoningEffort !== 'off' ? ANTHROPIC_THINKING_BUDGET[reasoningEffort] : undefined
|
|
595
|
+
const maxTokens = req.maxTokens ?? 4096
|
|
596
|
+
if (thinkingBudget !== undefined && thinkingBudget >= maxTokens) {
|
|
597
|
+
throw new Error(`CAR-E-LLM-REASONING: thinking budget_tokens(${thinkingBudget}) 必须小于 max_tokens(${maxTokens})——请调高 maxTokens(禁静默改写请求参数)`)
|
|
598
|
+
}
|
|
548
599
|
return {
|
|
549
600
|
method: 'POST' as const,
|
|
550
601
|
headers: {
|
|
@@ -555,7 +606,8 @@ function toAnthropic(req: LlmRequest, apiKey: string) {
|
|
|
555
606
|
body: JSON.stringify({
|
|
556
607
|
model: req.model,
|
|
557
608
|
stream: true,
|
|
558
|
-
max_tokens:
|
|
609
|
+
max_tokens: maxTokens,
|
|
610
|
+
...(thinkingBudget !== undefined ? { thinking: { type: 'enabled', budget_tokens: thinkingBudget } } : {}),
|
|
559
611
|
...(system.length ? { system: system.join('\n\n') } : {}),
|
|
560
612
|
messages: turns,
|
|
561
613
|
...(req.tools.length
|
|
@@ -639,6 +691,10 @@ export function createAnthropicAdapter(opts: AnthropicAdapterOptions): LlmAdapte
|
|
|
639
691
|
const firstByteMs = opts.firstByteMs ?? 30_000
|
|
640
692
|
const retries = opts.retries ?? 2
|
|
641
693
|
const backoffMs = opts.backoffMs ?? [1_000, 2_000]
|
|
694
|
+
// 1.14-S4(K-POOL-1):成员级 keyEnv 精确候选(同 openai-compat 口径)
|
|
695
|
+
const credResolveOpts = opts.credentialEnvVars?.length
|
|
696
|
+
? { envVarCandidates: opts.credentialEnvVars, allowEnvFallback: true }
|
|
697
|
+
: {}
|
|
642
698
|
|
|
643
699
|
return {
|
|
644
700
|
id,
|
|
@@ -650,7 +706,7 @@ export function createAnthropicAdapter(opts: AnthropicAdapterOptions): LlmAdapte
|
|
|
650
706
|
buildRequest: async () => {
|
|
651
707
|
let apiKey = ''
|
|
652
708
|
if (opts.credentials) {
|
|
653
|
-
const ref = opts.credentials.resolve(opts.provider ?? id,
|
|
709
|
+
const ref = opts.credentials.resolve(opts.provider ?? id, credResolveOpts)
|
|
654
710
|
apiKey = opts.credentials.reveal(ref, {})
|
|
655
711
|
}
|
|
656
712
|
return toAnthropic(req, apiKey)
|
|
@@ -25,6 +25,10 @@ export interface LlmRequest {
|
|
|
25
25
|
/** 1.4-S4(D-19):turn 取消信号(runTurn 的 {aborted} 形态透传)——适配器在 attempt 前 /
|
|
26
26
|
* chunk 间隙检查;外部取消 ≠ 失败(不 retry、不产 error chunk,静默收口由 chatStep 抛 TurnAborted) */
|
|
27
27
|
signal?: { aborted: boolean }
|
|
28
|
+
/** 1.14-S1(K-MP-1 / D-39):思考参数——适配器按家族映射(openai-compat = reasoning_effort;
|
|
29
|
+
* anthropic = thinking.budget_tokens);**off / 缺席 = 不下发该字段**(字节面零痕迹,非「effort:none」);
|
|
30
|
+
* 模型思考档位(模型级思考如 R1)为宿主侧模型元数据事实源(桌面 models:meta),内核不做档位判断 */
|
|
31
|
+
reasoning?: { effort: 'off' | 'low' | 'medium' | 'high' }
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
/** 工具调用流式增量(跨 chunk 聚合出完整 ToolCall) */
|
|
@@ -53,6 +57,13 @@ export interface LlmUsage {
|
|
|
53
57
|
adapterId: string
|
|
54
58
|
/** provider 原始 usage 对象(anthropic 为 messageStart/messageDelta 两原始片段) */
|
|
55
59
|
providerRaw: unknown
|
|
60
|
+
/**
|
|
61
|
+
* 1.14-S3(K-MP-2b / D-41):请求分段计量——仅采 provider 真实返回的分段事实(归一化后),
|
|
62
|
+
* **无分段事实 = 不带键(禁静态估算,桌面色条退化单色不造假)**。v1 分段 = reasoningTokens
|
|
63
|
+
* (openai completion_tokens_details.reasoning_tokens——K-MP-1 思考参数的计量配套,thinking
|
|
64
|
+
* 输出细分;anthropic thinking 计入 output_tokens 无细分事实 → 缺省不带键,登记)。
|
|
65
|
+
*/
|
|
66
|
+
segments?: { reasoningTokens?: number }
|
|
56
67
|
}
|
|
57
68
|
|
|
58
69
|
/** 流式块(finishReason 一经产生不可被任何中间层改写) */
|
package/src/sandbox/sandbox.ts
CHANGED
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
import { spawn } from 'node:child_process'
|
|
15
15
|
|
|
16
16
|
export type SandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access'
|
|
17
|
-
|
|
17
|
+
// 1.13.0(K-FS-3):加法 'fs-read' | 'fs-delete'——文件安全三轴的审计对象保真(能力×资源);
|
|
18
|
+
// 分类纪律:fs-delete 为写类(变更面,降级态 Q-04 强制 confirm),fs-read 为只读类(五枚举既有语义不动)
|
|
19
|
+
export type Capability = 'fs-write' | 'net' | 'exec' | 'mcp' | 'env-read' | 'fs-read' | 'fs-delete'
|
|
18
20
|
|
|
19
21
|
export interface ProbeResult {
|
|
20
22
|
platform: NodeJS.Platform
|
|
@@ -64,11 +66,22 @@ export interface ExecRequest {
|
|
|
64
66
|
/** 运行权限模式(F4):readonly/confirm/full——降级态下 full 亦受限为 confirm */
|
|
65
67
|
permissionMode: 'readonly' | 'confirm' | 'full'
|
|
66
68
|
authorize?: (req: ExecRequest) => Promise<boolean>
|
|
69
|
+
/**
|
|
70
|
+
* 1.13.0(K-SC-1):authorizeAll=true 将 confirm 模式的授权门扩展到非写类 argv
|
|
71
|
+
* (文件/命令规则轴的只读命令面——询问/拒绝规则可达);缺省 false = 既有行为零变化
|
|
72
|
+
* (readonly 模式契约不变:写类一律拒、只读类照常放行;full 整体旁路)。
|
|
73
|
+
*/
|
|
74
|
+
authorizeAll?: boolean
|
|
67
75
|
timeoutMs?: number
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
export interface ExecResult { ok: boolean; stdout: string; stderr: string; code: number | null; degraded: boolean; denied?: string }
|
|
71
79
|
|
|
80
|
+
/** 写类能力判定(1.13.0 收敛单点):fs-delete 为变更面归写类;fs-read 为只读类不归(K-FS-3 分类纪律) */
|
|
81
|
+
function isWriteish(c: Capability): boolean {
|
|
82
|
+
return c === 'fs-write' || c === 'net' || c === 'exec' || c === 'mcp' || c === 'fs-delete'
|
|
83
|
+
}
|
|
84
|
+
|
|
72
85
|
/**
|
|
73
86
|
* M2-S7 · Seatbelt profile 生成器(darwin 后端)
|
|
74
87
|
*
|
|
@@ -140,7 +153,7 @@ export class SandboxExecutor {
|
|
|
140
153
|
/** Q-04 定稿:降级态运行约束判定(在授权门之前生效) */
|
|
141
154
|
effectivePermission(permissionMode: ExecRequest['permissionMode'], capabilities: Capability[]): { mode: 'readonly' | 'confirm' | 'full'; denied: string | null } {
|
|
142
155
|
if (!this.degraded) return { mode: permissionMode, denied: null }
|
|
143
|
-
const writeish = capabilities.some(c => c
|
|
156
|
+
const writeish = capabilities.some(c => isWriteish(c))
|
|
144
157
|
if (capabilities.includes('env-read')) return { mode: 'confirm', denied: 'env-read denied in degraded sandbox(Q-04:一律拒绝)' }
|
|
145
158
|
if (writeish) return { mode: 'confirm', denied: null } // full 亦受限为 confirm
|
|
146
159
|
return { mode: permissionMode === 'full' ? 'confirm' : permissionMode, denied: null }
|
|
@@ -153,9 +166,11 @@ export class SandboxExecutor {
|
|
|
153
166
|
this.#audit({ kind: 'sandbox-denied', detail: { argv: req.argv, reason: degradedDenial, degraded: true }, ts: Date.now() })
|
|
154
167
|
return { ok: false, stdout: '', stderr: degradedDenial, code: -1, degraded: true, denied: degradedDenial }
|
|
155
168
|
}
|
|
156
|
-
// 权限门:写类能力在 readonly 一律拒;confirm 需审批(降级态强制 confirm
|
|
157
|
-
|
|
158
|
-
|
|
169
|
+
// 权限门:写类能力在 readonly 一律拒;confirm 需审批(降级态强制 confirm)。
|
|
170
|
+
// 1.13.0(K-SC-1):authorizeAll=true 且 confirm 时非写类 argv 同样进门(命令规则轴只读面)
|
|
171
|
+
const writeish = req.capabilities.some(c => isWriteish(c))
|
|
172
|
+
const gated = effMode !== 'full' && (writeish || (req.authorizeAll === true && effMode === 'confirm'))
|
|
173
|
+
if (gated) {
|
|
159
174
|
const granted = effMode === 'confirm' ? await (req.authorize?.(req) ?? Promise.resolve(false)) : false
|
|
160
175
|
if (!granted) {
|
|
161
176
|
this.#audit({ kind: 'sandbox-denied', detail: { argv: req.argv, mode: effMode, degraded: this.degraded }, ts: Date.now() })
|
package/src/seam.ts
CHANGED
|
@@ -12,8 +12,18 @@
|
|
|
12
12
|
* runCode(PTC 红线单点——erasable-only/预算/授权门仅经 makePtcToolDefinition 行为面可达)、JsonRpc 编码层、
|
|
13
13
|
* rawPost/parseTraceparent/formatTraceparent(trace 通道内部件)。jiti epoch 面(D-37):mountPlugin.reloadEpoch
|
|
14
14
|
* 自 1.10.0 起已随本面可达,零加法登记确认。
|
|
15
|
+
* 加法登记:1.13.0 —— 桌面 S3 治理面(文件安全设计 §7 / S3 迭代规划 D-S3-1):K-FS-4 redact 出面(fs_read 落链前
|
|
16
|
+
* 脱敏——链上不得进密钥原文)+ K-S3-1 loadSessionLog/deriveSessionId 出面(审计页历史会话链校验/取证导出的
|
|
17
|
+
* 只读载入面;载入/校验单一权威在内核,桌面禁自实现哈希重算)+ K-S3-3 exportForensicsBundle/verifyBundle 出面
|
|
18
|
+
* (取证包导出 + 读回重验;断链即中止语义随本面)。恢复面 K-S3-2 零登记确认:SessionLog.fromEvents 自 M5 DEC-4
|
|
19
|
+
* 起已随本面可达(fork.ts 生产调用点),S2 D-S2-6 前提修正登记(S3 执行期发现)。
|
|
20
|
+
* 加法登记:1.14.0 —— PluginManager 装载报告面(K-S4-1 / D-43):loadPlugins 出面(六阶段流水线
|
|
21
|
+
* discover/verify/parse/validate/topo/register——LoadReportVO 结构化 VO,verify/topo 真值取代 v0.1 桌面
|
|
22
|
+
* mountPlugin 组合呈现的 SKIPPED 标注)+ ReloadManager 出面(热重载 epoch 击穿 + invalidate 语义随本面)+
|
|
23
|
+
* verifyPluginFile 出面(签名校验挂载点——宿主自有装载路径可复用同一签名门;minisign 轨唯一实装,sigstore
|
|
24
|
+
* 主轨维持 verifier 骨架期预留——D-43 裁决:登记 strict 期限不引入 sigstore 依赖,engines 硬不兼容见评估报告)。
|
|
15
25
|
*/
|
|
16
|
-
export { SessionLog, type SessionEvent, type EventKind, type Actor } from './session/log.ts'
|
|
26
|
+
export { SessionLog, loadSessionLog, deriveSessionId, type SessionEvent, type EventKind, type Actor } from './session/log.ts'
|
|
17
27
|
export { SessionFileStore } from './session/store.ts'
|
|
18
28
|
export { runTurn, type TurnEndReason, type TurnPreset, type TurnResult } from './loop/stop.ts'
|
|
19
29
|
export { chatStep } from './runtime-core/chatStep.ts'
|
|
@@ -34,3 +44,9 @@ export { createHttpMcpTransport, resolveMcpHeaders, type HttpMcpServerSpec } fro
|
|
|
34
44
|
export { makePtcToolDefinition, type ToolBridge } from './ptc/runCode.ts'
|
|
35
45
|
export { renderSdkFromRegistry } from './ptc/sdk.ts'
|
|
36
46
|
export type { McpClientSpanHandle, PtcRelayHandle } from './runtime-core/trace.ts'
|
|
47
|
+
// —— 1.13.0:桌面 S3 治理面(文件安全/审计/取证)——
|
|
48
|
+
export { redact, type RedactionResult } from './runtime-core/redaction.ts'
|
|
49
|
+
export { exportForensicsBundle, verifyBundle, type ForensicsBundle, type ExportMeta } from './session/export.ts'
|
|
50
|
+
// —— 1.14.0(K-S4-1 / D-43):PluginManager 装载报告面 + 签名校验挂载点 ——
|
|
51
|
+
export { loadPlugins, ReloadManager, type LoadReportVO, type LoadPluginsResult, type LoadPluginsOptions, type LoadStage, type StageStatus, type PeerConflictChain } from './load/report.ts'
|
|
52
|
+
export { verifyPluginFile, type SignatureGateOptions, type SigGateResult } from './load/sigGate.ts'
|
|
@@ -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
|
+
}
|
package/src/session/log.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { createHash } from 'node:crypto'
|
|
|
11
11
|
import { readFileSync } from 'node:fs'
|
|
12
12
|
import { decodeLogBuffer, splitJsonlLines } from './format.ts'
|
|
13
13
|
|
|
14
|
-
export type EventKind = 'user' | 'assistant' | 'toolCall' | 'toolResult' | 'turnEnd' | 'goalUpdate' | 'hostRaw' | 'fork' | 'system' | 'usage' // 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
|
|
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
|
-
|
|
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":"MEUCIA0UBnqeM+TPTKLaxwsFDiHv1WnpL4aJ5GHqxXzLSbAyAiEAneTvQOA349AWwfG/eCWATHXznws2AYYf/EXE/cVQrGU=","cert":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUc1akNDQm0yZ0F3SUJBZ0lVV204L2E1cGFQWmpYcXRXVmRoUnlDeWMrZk1Jd0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpZeE1EQTNNRGswTURVMFdoY05Nall4TURBM01EazFNRFUwV2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVXMFVLYVFXcUF5NjMvZlB4alo4ak83Yndtekk5WmlUNkEwN1YKTXZxTzlxdnJZQjloR2tZaGNWRDcza25jdUpacmQrVkhNL3BJNmEwWDZjR290QzNjNGFPQ0JZd3dnZ1dJTUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWZCZ05WSFNNRUdEQVdnQlRmCjArblBWaVFSbHZtbzJPa29WYUxHTGhoa1B6QmlCZ05WSFJFQkFmOEVXREJXaGxSb2RIUndjem92TDJkcGRHaDEKWWk1amIyMHZiSEZqTmpJMEwyTmhjaTF5ZFc1MGFXMWxMeTVuYVhSb2RXSXZkMjl5YTJac2IzZHpMM0psYkdWaApjMlV1ZVcxc1FISmxabk12ZEdGbmN5OHhMakV5TGpBd09RWUtLd1lCQkFHRHZ6QUJBUVFyYUhSMGNITTZMeTkwCmIydGxiaTVoWTNScGIyNXpMbWRwZEdoMVluVnpaWEpqYjI1MFpXNTBMbU52YlRBVkJnb3JCZ0VFQVlPL01BRUMKQkFkeVpXeGxZWE5sTURZR0Npc0dBUVFCZzc4d0FRTUVLRFJtWldVMlpHRXpNbVZtT1dReU9EbGpaV0k0TUdObQpZekZsTnpNNE0yUXdPVFZpWVRoa05qa3dGUVlLS3dZQkJBR0R2ekFCQkFRSGNtVnNaV0Z6WlRBZ0Jnb3JCZ0VFCkFZTy9NQUVGQkJKc2NXTTJNalF2WTJGeUxYSjFiblJwYldVd0hnWUtLd1lCQkFHRHZ6QUJCZ1FRY21WbWN5OTAKWVdkekx6RXVNVEl1TURBN0Jnb3JCZ0VFQVlPL01BRUlCQzBNSzJoMGRIQnpPaTh2ZEc5clpXNHVZV04wYVc5dQpjeTVuYVhSb2RXSjFjMlZ5WTI5dWRHVnVkQzVqYjIwd1pBWUtLd1lCQkFHRHZ6QUJDUVJXREZSb2RIUndjem92CkwyZHBkR2gxWWk1amIyMHZiSEZqTmpJMEwyTmhjaTF5ZFc1MGFXMWxMeTVuYVhSb2RXSXZkMjl5YTJac2IzZHoKTDNKbGJHVmhjMlV1ZVcxc1FISmxabk12ZEdGbmN5OHhMakV5TGpBd09BWUtLd1lCQkFHRHZ6QUJDZ1FxRENnMApabVZsTm1SaE16SmxaamxrTWpnNVkyVmlPREJqWm1NeFpUY3pPRE5rTURrMVltRTRaRFk1TUIwR0Npc0dBUVFCCmc3OHdBUXNFRHd3TloybDBhSFZpTFdodmMzUmxaREExQmdvckJnRUVBWU8vTUFFTUJDY01KV2gwZEhCek9pOHYKWjJsMGFIVmlMbU52YlM5c2NXTTJNalF2WTJGeUxYSjFiblJwYldVd09BWUtLd1lCQkFHRHZ6QUJEUVFxRENnMApabVZsTm1SaE16SmxaamxrTWpnNVkyVmlPREJqWm1NeFpUY3pPRE5rTURrMVltRTRaRFk1TUNBR0Npc0dBUVFCCmc3OHdBUTRFRWd3UWNtVm1jeTkwWVdkekx6RXVNVEl1TURBYUJnb3JCZ0VFQVlPL01BRVBCQXdNQ2pFek5qRXgKT1RJM01UUXdLUVlLS3dZQkJBR0R2ekFCRUFRYkRCbG9kSFJ3Y3pvdkwyZHBkR2gxWWk1amIyMHZiSEZqTmpJMApNQmdHQ2lzR0FRUUJnNzh3QVJFRUNnd0lOamt5TXpRMU5qUXdaQVlLS3dZQkJBR0R2ekFCRWdSV0RGUm9kSFJ3CmN6b3ZMMmRwZEdoMVlpNWpiMjB2YkhGak5qSTBMMk5oY2kxeWRXNTBhVzFsTHk1bmFYUm9kV0l2ZDI5eWEyWnMKYjNkekwzSmxiR1ZoYzJVdWVXMXNRSEpsWm5NdmRHRm5jeTh4TGpFeUxqQXdPQVlLS3dZQkJBR0R2ekFCRXdRcQpEQ2cwWm1WbE5tUmhNekpsWmpsa01qZzVZMlZpT0RCalptTXhaVGN6T0ROa01EazFZbUU0WkRZNU1CY0dDaXNHCkFRUUJnNzh3QVJRRUNRd0hjbVZzWldGelpUQlpCZ29yQmdFRUFZTy9NQUVWQkVzTVNXaDBkSEJ6T2k4dloybDAKYUhWaUxtTnZiUzlzY1dNMk1qUXZZMkZ5TFhKMWJuUnBiV1V2WVdOMGFXOXVjeTl5ZFc1ekx6TTNOakF5TWpZeQpOakl4TDJGMGRHVnRjSFJ6THpFd0ZnWUtLd1lCQkFHRHZ6QUJGZ1FJREFad2RXSnNhV013VUFZS0t3WUJCQUdECnZ6QUJHQVJDREVCeVpYQnZPbXh4WXpZeU5FQTJPVEl6TkRVMk5DOWpZWEl0Y25WdWRHbHRaVUF4TXpZeE1Ua3kKTnpFME9uSmxaanB5WldaekwzUmhaM012TVM0eE1pNHdNSUdLQmdvckJnRUVBZFo1QWdRQ0JId0VlZ0I0QUhZQQozVDB3YXNiSEVUSmpHUjRjbVdjM0FxSktYcmplUEszL2g0cHlnQzhwN280QUFBR2hGYnpBd1FBQUJBTUFSekJGCkFpQTNSUk0xdk1iZDBmVUU0VUlvMjFYaUo5a0lMYThlUnE1VlBpaHpHcGNybmdJaEFKZlNzQ01GVGpFYk11L0kKdmh1L3VnR1l4d3J2S0dZcGpXVytKUnBtUDF3WU1Bb0dDQ3FHU000OUJBTURBMmNBTUdRQ01Id3FjdWp4Nm96KwpibTlwZ1E1SWZoTXZmVW1qWUdlaG5vL3EvSU9wSnhsU0hESStxSTljc0lENFltUDhGLzdVa1FJd1ZnMCs3UmFJClRncnR1V2laaFhTQWprdmgvdUU1clhZMUdiZ0FQOGd3RUxhdmFCMTdhbXBORXBhU3Q3Nkw0Tzk0Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K","rekorBundle":{"SignedEntryTimestamp":"MEUCIQDK0u1UYH5gd1wwowDNOGkRWm7g3kMOt76q+73QtUfU1gIgI8/8XsM4m3rnPXnGT9A9PmHLMpheIYj76cODbAeO9Ow=","Payload":{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJhM2Q5N2YzZmE3ZWI0NTViYmEwZmVkNzllZDY4NGUwYTMxNGQ5YjMzZWQ3NWVjNGNhN2EzMDAzNzA1ODgxZjEyIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJQTBVQm5xZU0rVFBUS0xheHdzRkRpSHYxV25wTDRhSjVHSHF4WHpMU2JBeUFpRUFuZVR2UU9BMzQ5QVd3ZkcvZUNXQVRIWHpud3MyQVlZZi9FWEUvY1ZRckdVPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVYzFha05EUW0weVowRjNTVUpCWjBsVlYyMDRMMkUxY0dGUVdtcFljWFJYVm1Sb1VubERlV01yWmsxSmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFplRTFFUVROTlJHc3dUVVJWTUZkb1kwNU5hbGw0VFVSQk0wMUVhekZOUkZVd1YycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZYTUZWTFlWRlhjVUY1TmpNdlpsQjRhbG80YWs4M1luZHRla2s1V21sVU5rRXdOMVlLVFhaeFR6bHhkbkpaUWpsb1IydFphR05XUkRjemEyNWpkVXBhY21RclZraE5MM0JKTm1Fd1dEWmpSMjkwUXpOak5HRlBRMEpaZDNkbloxZEpUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXWkNaMDVXU0ZOTlJVZEVRVmRuUWxSbUNqQXJibEJXYVZGU2JIWnRiekpQYTI5V1lVeEhUR2hvYTFCNlFtbENaMDVXU0ZKRlFrRm1PRVZYUkVKWGFHeFNiMlJJVW5kamVtOTJUREprY0dSSGFERUtXV2sxYW1JeU1IWmlTRVpxVG1wSk1Fd3lUbWhqYVRGNVpGYzFNR0ZYTVd4TWVUVnVZVmhTYjJSWFNYWmtNamw1WVRKYWMySXpaSHBNTTBwc1lrZFdhQXBqTWxWMVpWY3hjMUZJU214YWJrMTJaRWRHYm1ONU9IaE1ha1Y1VEdwQmQwOVJXVXRMZDFsQ1FrRkhSSFo2UVVKQlVWRnlZVWhTTUdOSVRUWk1lVGt3Q21JeWRHeGlhVFZvV1ROU2NHSXlOWHBNYldSd1pFZG9NVmx1Vm5wYVdFcHFZakkxTUZwWE5UQk1iVTUyWWxSQlZrSm5iM0pDWjBWRlFWbFBMMDFCUlVNS1FrRmtlVnBYZUd4WldFNXNUVVJaUjBOcGMwZEJVVkZDWnpjNGQwRlJUVVZMUkZKdFdsZFZNbHBIUlhwTmJWWnRUMWRSZVU5RWJHcGFWMGswVFVkT2JRcFpla1pzVG5wTk5FMHlVWGRQVkZacFdWUm9hMDVxYTNkR1VWbExTM2RaUWtKQlIwUjJla0ZDUWtGUlNHTnRWbk5hVjBaNldsUkJaMEpuYjNKQ1owVkZDa0ZaVHk5TlFVVkdRa0pLYzJOWFRUSk5hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFZkMGhuV1V0TGQxbENRa0ZIUkhaNlFVSkNaMUZSWTIxV2JXTjVPVEFLV1Zka2VreDZSWFZOVkVsMVRVUkJOMEpuYjNKQ1owVkZRVmxQTDAxQlJVbENRekJOU3pKb01HUklRbnBQYVRoMlpFYzVjbHBYTkhWWlYwNHdZVmM1ZFFwamVUVnVZVmhTYjJSWFNqRmpNbFo1V1RJNWRXUkhWblZrUXpWcVlqSXdkMXBCV1V0TGQxbENRa0ZIUkhaNlFVSkRVVkpYUkVaU2IyUklVbmRqZW05MkNrd3laSEJrUjJneFdXazFhbUl5TUhaaVNFWnFUbXBKTUV3eVRtaGphVEY1WkZjMU1HRlhNV3hNZVRWdVlWaFNiMlJYU1haa01qbDVZVEphYzJJelpIb0tURE5LYkdKSFZtaGpNbFYxWlZjeGMxRklTbXhhYmsxMlpFZEdibU41T0hoTWFrVjVUR3BCZDA5QldVdExkMWxDUWtGSFJIWjZRVUpEWjFGeFJFTm5NQXBhYlZac1RtMVNhRTE2U214YWFteHJUV3BuTlZreVZtbFBSRUpxV20xTmVGcFVZM3BQUkU1clRVUnJNVmx0UlRSYVJGazFUVUl3UjBOcGMwZEJVVkZDQ21jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacFRGZG9kbU16VW14YVJFRXhRbWR2Y2tKblJVVkJXVTh2VFVGRlRVSkRZMDFLVjJnd1pFaENlazlwT0hZS1dqSnNNR0ZJVm1sTWJVNTJZbE01YzJOWFRUSk5hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFZkMDlCV1V0TGQxbENRa0ZIUkhaNlFVSkVVVkZ4UkVObk1BcGFiVlpzVG0xU2FFMTZTbXhhYW14clRXcG5OVmt5Vm1sUFJFSnFXbTFOZUZwVVkzcFBSRTVyVFVSck1WbHRSVFJhUkZrMVRVTkJSME5wYzBkQlVWRkNDbWMzT0hkQlVUUkZSV2QzVVdOdFZtMWplVGt3V1Zka2VreDZSWFZOVkVsMVRVUkJZVUpuYjNKQ1owVkZRVmxQTDAxQlJWQkNRWGROUTJwRmVrNXFSWGdLVDFSSk0wMVVVWGRMVVZsTFMzZFpRa0pCUjBSMmVrRkNSVUZSWWtSQ2JHOWtTRkozWTNwdmRrd3laSEJrUjJneFdXazFhbUl5TUhaaVNFWnFUbXBKTUFwTlFtZEhRMmx6UjBGUlVVSm5OemgzUVZKRlJVTm5kMGxPYW10NVRYcFJNVTVxVVhkYVFWbExTM2RaUWtKQlIwUjJla0ZDUldkU1YwUkdVbTlrU0ZKM0NtTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJZa2hHYWs1cVNUQk1NazVvWTJreGVXUlhOVEJoVnpGc1RIazFibUZZVW05a1YwbDJaREk1ZVdFeVduTUtZak5rZWt3elNteGlSMVpvWXpKVmRXVlhNWE5SU0Vwc1dtNU5kbVJIUm01amVUaDRUR3BGZVV4cVFYZFBRVmxMUzNkWlFrSkJSMFIyZWtGQ1JYZFJjUXBFUTJjd1dtMVdiRTV0VW1oTmVrcHNXbXBzYTAxcVp6VlpNbFpwVDBSQ2FscHRUWGhhVkdONlQwUk9hMDFFYXpGWmJVVTBXa1JaTlUxQ1kwZERhWE5IQ2tGUlVVSm5OemgzUVZKUlJVTlJkMGhqYlZaeldsZEdlbHBVUWxwQ1oyOXlRbWRGUlVGWlR5OU5RVVZXUWtWelRWTlhhREJrU0VKNlQyazRkbG95YkRBS1lVaFdhVXh0VG5aaVV6bHpZMWROTWsxcVVYWlpNa1o1VEZoS01XSnVVbkJpVjFWMldWZE9NR0ZYT1hWamVUbDVaRmMxZWt4NlRUTk9ha0Y1VFdwWmVRcE9ha2w0VERKR01HUkhWblJqU0ZKNlRIcEZkMFpuV1V0TGQxbENRa0ZIUkhaNlFVSkdaMUZKUkVGYWQyUlhTbk5oVjAxM1ZVRlpTMHQzV1VKQ1FVZEVDblo2UVVKSFFWSkRSRVZDZVZwWVFuWlBiWGg0V1hwWmVVNUZRVEpQVkVsNlRrUlZNazVET1dwWldFbDBZMjVXZFdSSGJIUmFWVUY0VFhwWmVFMVVhM2tLVG5wRk1FOXVTbXhhYW5CNVdsZGFla3d6VW1oYU0wMTJUVk0wZUUxcE5IZE5TVWRMUW1kdmNrSm5SVVZCWkZvMVFXZFJRMEpJZDBWbFowSTBRVWhaUVFvelZEQjNZWE5pU0VWVVNtcEhValJqYlZkak0wRnhTa3RZY21wbFVFc3pMMmcwY0hsblF6aHdOMjgwUVVGQlIyaEdZbnBCZDFGQlFVSkJUVUZTZWtKR0NrRnBRVE5TVWsweGRrMWlaREJtVlVVMFZVbHZNakZZYVVvNWEwbE1ZVGhsVW5FMVZsQnBhSHBIY0dOeWJtZEphRUZLWmxOelEwMUdWR3BGWWsxMUwwa0tkbWgxTDNWblIxbDRkM0oyUzBkWmNHcFhWeXRLVW5CdFVERjNXVTFCYjBkRFEzRkhVMDAwT1VKQlRVUkJNbU5CVFVkUlEwMUlkM0ZqZFdwNE5tOTZLd3BpYlRsd1oxRTFTV1pvVFhabVZXMXFXVWRsYUc1dkwzRXZTVTl3U25oc1UwaEVTU3R4U1RsamMwbEVORmx0VURoR0x6ZFZhMUZKZDFabk1DczNVbUZKQ2xSbmNuUjFWMmxhYUZoVFFXcHJkbWd2ZFVVMWNsaFpNVWRpWjBGUU9HZDNSVXhoZG1GQ01UZGhiWEJPUlhCaFUzUTNOa3cwVHprMENpMHRMUzB0UlU1RUlFTkZVbFJKUmtsRFFWUkZMUzB0TFMwSyJ9fX19","integratedTime":1791366054,"logIndex":3128454895,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}}
|