@lqc123qwe/car-runtime 1.13.0 → 1.14.1

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/sbom.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "bomFormat": "CAR-SBOM",
3
3
  "specVersion": "0.1",
4
- "version": "1.13.0",
4
+ "version": "1.14.1",
5
5
  "components": [
6
6
  {
7
7
  "type": "file",
@@ -11,7 +11,7 @@
11
11
  {
12
12
  "type": "file",
13
13
  "path": "src/cli.ts",
14
- "sha256": "fc52749c25ac8534bed687e33ae4c32ecbaf221d11b38af3477191779b6b5b36"
14
+ "sha256": "950c904e6a5dc05ae130b997bf93bcd8ea6a00dfe10b8bc36796641298f8fafa"
15
15
  },
16
16
  {
17
17
  "type": "file",
@@ -56,13 +56,18 @@
56
56
  {
57
57
  "type": "file",
58
58
  "path": "src/load/config.ts",
59
- "sha256": "60b0b2c4bd97a75f3c31e59fe4d1a44acbc0d2ca6167bd0394c2744d63a4d8e2"
59
+ "sha256": "2cb17ffa5da642ffdb17a2b067d10c421129534366bbe1f41687e1d8a5aeaab3"
60
60
  },
61
61
  {
62
62
  "type": "file",
63
63
  "path": "src/load/loader.ts",
64
64
  "sha256": "61302590522da92501e3927b2dc6551e86e8a8ef9e79db8b10865ba73e978246"
65
65
  },
66
+ {
67
+ "type": "file",
68
+ "path": "src/load/pool.ts",
69
+ "sha256": "e52909bcad4ba2270e62cb1f509abb6fd6e2e017e2f1b0ce8f326ade8d876ee3"
70
+ },
66
71
  {
67
72
  "type": "file",
68
73
  "path": "src/load/registry.ts",
@@ -101,7 +106,7 @@
101
106
  {
102
107
  "type": "file",
103
108
  "path": "src/loop/stop.ts",
104
- "sha256": "d9290d51abf81211806c4491a65cfc9af5466a1dd8e32a497237f54a76f8c947"
109
+ "sha256": "e24206282b2821e4bde36352a988ec871de3d7d1c57bb3a4b30801c56b152734"
105
110
  },
106
111
  {
107
112
  "type": "file",
@@ -151,12 +156,12 @@
151
156
  {
152
157
  "type": "file",
153
158
  "path": "src/runtime-core/chatStep.ts",
154
- "sha256": "7a0f455e352529ee7e97a3ed2b9b7b9032493baae7542396d6f8b186fb788ecb"
159
+ "sha256": "b4a4715b957a43513e7e645b8e64b1d1d2e99b52f64c619e9bc45f91364531d3"
155
160
  },
156
161
  {
157
162
  "type": "file",
158
163
  "path": "src/runtime-core/credentials.ts",
159
- "sha256": "f59d8420e5066d915f688f594b447026b992fa70980600a500773e3a0de2d1b1"
164
+ "sha256": "0fc305cb00de100eb807e9a32f9c893fed0f33a89f1de9526d8e2e7e7711cddd"
160
165
  },
161
166
  {
162
167
  "type": "file",
@@ -166,7 +171,7 @@
166
171
  {
167
172
  "type": "file",
168
173
  "path": "src/runtime-core/llm.ts",
169
- "sha256": "781b951a4e822c759fc7af0297679d4f000270162e3c3b1ff119c03e59e91cce"
174
+ "sha256": "3d203bae2ade0fdde8094e5a4b181fb759264d043eb020fde5b62ff4220cda1d"
170
175
  },
171
176
  {
172
177
  "type": "file",
@@ -191,7 +196,7 @@
191
196
  {
192
197
  "type": "file",
193
198
  "path": "src/runtime-core/types.ts",
194
- "sha256": "f828cc38072b36ba47d8a4f24337277fe5e5867fa0292a24696c9f845f204571"
199
+ "sha256": "15229f894f8432ce29e8c1b3dcefded54a1e1220e0c6da93c5f24a68213147df"
195
200
  },
196
201
  {
197
202
  "type": "file",
@@ -201,7 +206,7 @@
201
206
  {
202
207
  "type": "file",
203
208
  "path": "src/seam.ts",
204
- "sha256": "835acb31893bbfa99d2dbab8fea9a0598608b493c00b8b77094e9d0a14812f83"
209
+ "sha256": "0a4a231c9ab3ad5898a9a22fd9e53e17a8261d3d4057940b7d2148325573006c"
205
210
  },
206
211
  {
207
212
  "type": "file",
@@ -213,6 +218,11 @@
213
218
  "path": "src/security/secrets.ts",
214
219
  "sha256": "9c9c95c9c9d5cc7b9c31b5aada49e556417882ade56df40d90967c900a209a06"
215
220
  },
221
+ {
222
+ "type": "file",
223
+ "path": "src/session/compact.ts",
224
+ "sha256": "e79bb07f389f0797cbd12ed44f2e0f3e277b179b6b3b2fc698edd4698925aedf"
225
+ },
216
226
  {
217
227
  "type": "file",
218
228
  "path": "src/session/export.ts",
@@ -236,7 +246,7 @@
236
246
  {
237
247
  "type": "file",
238
248
  "path": "src/session/log.ts",
239
- "sha256": "aa8c7b8d9cff690dfe7c6429399d9d2bd656f1ff1c07154715e4dc5d07946c9d"
249
+ "sha256": "9295cf0c8a7452324a0661e1d6fd2872f6289c614c8f50596e82713f35988fdb"
240
250
  },
241
251
  {
242
252
  "type": "file",
@@ -251,12 +261,12 @@
251
261
  {
252
262
  "type": "file",
253
263
  "path": "package.json",
254
- "sha256": "0e011fa0b4c3f5eab169670cc185b2d4a2cdfa8d1523d9a8c4467192d659ed3c"
264
+ "sha256": "b637e74c6e9d878a3b53ba2481a15b1c3f735bf9341e9e29724c53708b35a6f1"
255
265
  },
256
266
  {
257
267
  "type": "file",
258
268
  "path": "README.md",
259
- "sha256": "37b32001af9f114bfbcee1a9f34b09a293fb76b1ef70a05ad7a21f95f6386c37"
269
+ "sha256": "d2e3490a5a0ef970884c038e4a385853fa8b293b85d7a65c1da5ef8edaa2a631"
260
270
  }
261
271
  ],
262
272
  "runtimeDependencies": []
package/dist/seam.d.ts CHANGED
@@ -17,6 +17,15 @@
17
17
  * 只读载入面;载入/校验单一权威在内核,桌面禁自实现哈希重算)+ K-S3-3 exportForensicsBundle/verifyBundle 出面
18
18
  * (取证包导出 + 读回重验;断链即中止语义随本面)。恢复面 K-S3-2 零登记确认:SessionLog.fromEvents 自 M5 DEC-4
19
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 硬不兼容见评估报告)。
25
+ * 加法登记:1.14.1 —— B2 前置补出面(1.14 消费面完成):K-MP-2 压缩行为 API(shouldCompact/applyCompaction
26
+ * + CompactionConfig——自动挂载点在宿主装配层〔runTurn 之间〕,立即压缩 API 独立可用,append-only 红线随本面)
27
+ * + K-POOL-1 池装配行为 API(assembleModelPool/probePoolMember + PoolMember/RoutingDecision 类型——
28
+ * 桌面模型池设置 UI 与 doctor 池健康行消费面;routing/decide 落链由宿主接线 onDecide)。
20
29
  */
21
30
  export { SessionLog, loadSessionLog, deriveSessionId, type SessionEvent, type EventKind, type Actor } from './session/log.ts';
22
31
  export { SessionFileStore } from './session/store.ts';
@@ -40,3 +49,7 @@ export { renderSdkFromRegistry } from './ptc/sdk.ts';
40
49
  export type { McpClientSpanHandle, PtcRelayHandle } from './runtime-core/trace.ts';
41
50
  export { redact, type RedactionResult } from './runtime-core/redaction.ts';
42
51
  export { exportForensicsBundle, verifyBundle, type ForensicsBundle, type ExportMeta } from './session/export.ts';
52
+ export { loadPlugins, ReloadManager, type LoadReportVO, type LoadPluginsResult, type LoadPluginsOptions, type LoadStage, type StageStatus, type PeerConflictChain } from './load/report.ts';
53
+ export { verifyPluginFile, type SignatureGateOptions, type SigGateResult } from './load/sigGate.ts';
54
+ export { shouldCompact, applyCompaction, type CompactionConfig } from './session/compact.ts';
55
+ export { assembleModelPool, probePoolMember, type PoolMember, type PoolMemberHealth, type RoutingDecision, type RoutingAttempt } from './load/pool.ts';
package/dist/seam.js CHANGED
@@ -17,6 +17,15 @@
17
17
  * 只读载入面;载入/校验单一权威在内核,桌面禁自实现哈希重算)+ K-S3-3 exportForensicsBundle/verifyBundle 出面
18
18
  * (取证包导出 + 读回重验;断链即中止语义随本面)。恢复面 K-S3-2 零登记确认:SessionLog.fromEvents 自 M5 DEC-4
19
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 硬不兼容见评估报告)。
25
+ * 加法登记:1.14.1 —— B2 前置补出面(1.14 消费面完成):K-MP-2 压缩行为 API(shouldCompact/applyCompaction
26
+ * + CompactionConfig——自动挂载点在宿主装配层〔runTurn 之间〕,立即压缩 API 独立可用,append-only 红线随本面)
27
+ * + K-POOL-1 池装配行为 API(assembleModelPool/probePoolMember + PoolMember/RoutingDecision 类型——
28
+ * 桌面模型池设置 UI 与 doctor 池健康行消费面;routing/decide 落链由宿主接线 onDecide)。
20
29
  */
21
30
  export { SessionLog, loadSessionLog, deriveSessionId } from './session/log.js';
22
31
  export { SessionFileStore } from './session/store.js';
@@ -40,3 +49,9 @@ export { renderSdkFromRegistry } from './ptc/sdk.js';
40
49
  // —— 1.13.0:桌面 S3 治理面(文件安全/审计/取证)——
41
50
  export { redact } from './runtime-core/redaction.js';
42
51
  export { exportForensicsBundle, verifyBundle } from './session/export.js';
52
+ // —— 1.14.0(K-S4-1 / D-43):PluginManager 装载报告面 + 签名校验挂载点 ——
53
+ export { loadPlugins, ReloadManager } from './load/report.js';
54
+ export { verifyPluginFile } from './load/sigGate.js';
55
+ // —— 1.14.1(B2 前置补出面):K-MP-2 压缩行为 API + K-POOL-1 池装配行为 API(1.14 消费面完成)——
56
+ export { shouldCompact, applyCompaction } from './session/compact.js';
57
+ export { assembleModelPool, probePoolMember } from './load/pool.js';
@@ -0,0 +1,57 @@
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
+ export interface CompactionConfig {
24
+ /** 缺省 false = 行为零变化(1.13 兼容线) */
25
+ enabled?: boolean;
26
+ /** 距上次压缩以来的新增事件数阈值(事实计数,非 token 估算);缺省 200 */
27
+ thresholdEvents?: number;
28
+ /** 保护最近 N 个 turn 不折叠;缺省 4 */
29
+ protectLastTurns?: number;
30
+ }
31
+ export interface CompactionPlan {
32
+ /** 折叠前缀边界 [0, compactedUpTo)——compact 事件 payload 原样携带 */
33
+ compactedUpTo: number;
34
+ /** 折叠区间覆盖的 turn 数(去重) */
35
+ compactedTurns: number;
36
+ /** 折叠区间事件引用(供摘要生成;只读) */
37
+ events: readonly SessionEvent[];
38
+ }
39
+ /** shouldCompact:距上次压缩的新增事件数 ≥ thresholdEvents(enabled 缺省关 = 零变化) */
40
+ export declare function shouldCompact(log: SessionLog, cfg: CompactionConfig | undefined): boolean;
41
+ /**
42
+ * planCompaction:计算折叠区间(纯函数,不落链)。
43
+ * 保护段 = 首个 system 事件 + 最近 protectLastTurns 个 turn(按 turnId 首次出现序);
44
+ * 边界回退保证 toolCall↔toolResult 成对(toolPairIntegrity);无可压缩区间显式报错。
45
+ */
46
+ export declare function planCompaction(log: SessionLog, protectLastTurns?: number): CompactionPlan;
47
+ /** 确定性摘要(缺省生成器):事实拼装(轮次意图 + 工具统计 + 收口状态),禁估算禁 LLM 依赖 */
48
+ export declare function defaultSummarizer(events: readonly SessionEvent[]): string;
49
+ /**
50
+ * applyCompaction:执行压缩(唯一落链动作 = append 一条 'compact' 事件——原事件链不改写)。
51
+ * summarize 缝:宿主可注入 LLM 摘要(返回非空字符串;缺省确定性生成器)。
52
+ */
53
+ export declare function applyCompaction(log: SessionLog, opts?: {
54
+ protectLastTurns?: number;
55
+ turnId?: string;
56
+ summarize?: (events: readonly SessionEvent[]) => string;
57
+ }): SessionEvent;
@@ -0,0 +1,128 @@
1
+ /** 上次压缩边界(无 compact 事件 = 0)——重复压缩不越界(幂等区间的下界) */
2
+ function lastCompactedUpTo(log) {
3
+ let last = 0;
4
+ for (const e of log.events) {
5
+ if (e.kind !== 'compact')
6
+ continue;
7
+ const p = (e.payload ?? {});
8
+ if (typeof p.compactedUpTo === 'number' && p.compactedUpTo > last)
9
+ last = p.compactedUpTo;
10
+ }
11
+ return last;
12
+ }
13
+ /** shouldCompact:距上次压缩的新增事件数 ≥ thresholdEvents(enabled 缺省关 = 零变化) */
14
+ export function shouldCompact(log, cfg) {
15
+ if (!cfg || cfg.enabled !== true)
16
+ return false;
17
+ const threshold = cfg.thresholdEvents ?? 200;
18
+ return log.events.length - lastCompactedUpTo(log) >= threshold;
19
+ }
20
+ /**
21
+ * planCompaction:计算折叠区间(纯函数,不落链)。
22
+ * 保护段 = 首个 system 事件 + 最近 protectLastTurns 个 turn(按 turnId 首次出现序);
23
+ * 边界回退保证 toolCall↔toolResult 成对(toolPairIntegrity);无可压缩区间显式报错。
24
+ */
25
+ export function planCompaction(log, protectLastTurns = 4) {
26
+ const events = log.events;
27
+ const floor = lastCompactedUpTo(log); // 上界不越过上次压缩边界(幂等)
28
+ // 保护起点候选:最近 N 个 turn 的首事件 seq
29
+ const turnFirst = new Map();
30
+ for (const e of events) {
31
+ if (e.turnId === 'compaction')
32
+ continue; // 压缩事件自身不构成 turn
33
+ if (!turnFirst.has(e.turnId))
34
+ turnFirst.set(e.turnId, e.seq);
35
+ }
36
+ const recentTurns = [...turnFirst.entries()].slice(-protectLastTurns);
37
+ let protectStart = recentTurns.length ? Math.min(...recentTurns.map(([, seq]) => seq)) : events.length;
38
+ // 统一规则:折叠区间 = [lowerBound, protectStart),lowerBound = max(上次压缩边界, 首个 system.seq + 1)
39
+ // ——首个 system(会话级提示)恒不被折叠;floor 已含它时(上次压缩边界在其后)不受限
40
+ const firstSystem = events.find(e => e.kind === 'system');
41
+ const lowerBound = firstSystem ? Math.max(floor, firstSystem.seq + 1) : floor;
42
+ if (protectStart <= lowerBound) {
43
+ throw new Error(`CAR-E-COMPACT-NORANGE: 无可压缩区间(保护段 = 首个 system + 最近 ${protectLastTurns} turn,上次压缩边界 = ${floor})——禁静默成功`);
44
+ }
45
+ // toolPairIntegrity:边界落在配对中间 → 回退到 toolCall(成对整体移出),循环至不动点
46
+ for (let guard = 0; guard < 1000; guard++) {
47
+ const callIds = new Map(); // id → call seq(区间内)
48
+ for (let i = lowerBound; i < protectStart; i++) {
49
+ const e = events[i];
50
+ if (e.kind === 'toolCall') {
51
+ const id = e.payload?.id;
52
+ if (typeof id === 'string')
53
+ callIds.set(id, e.seq);
54
+ }
55
+ }
56
+ let earliestCallSeq;
57
+ for (let i = protectStart; i < events.length; i++) {
58
+ const e = events[i];
59
+ if (e.kind !== 'toolResult')
60
+ continue;
61
+ const id = e.payload?.id;
62
+ if (typeof id === 'string' && callIds.has(id)) {
63
+ const callSeq = callIds.get(id);
64
+ if (earliestCallSeq === undefined || callSeq < earliestCallSeq)
65
+ earliestCallSeq = callSeq;
66
+ }
67
+ }
68
+ if (earliestCallSeq === undefined)
69
+ break; // 无破裂配对——边界成立
70
+ protectStart = earliestCallSeq; // 成对整体移出
71
+ if (protectStart <= lowerBound) {
72
+ throw new Error(`CAR-E-COMPACT-NORANGE: 工具对回退后无区间(toolCall↔toolResult 成对完整性不可满足)——禁静默成功`);
73
+ }
74
+ }
75
+ const compacted = events.slice(lowerBound, protectStart);
76
+ const turns = new Set(compacted.map(e => e.turnId));
77
+ return { compactedUpTo: protectStart, compactedTurns: turns.size, events: compacted };
78
+ }
79
+ /** 确定性摘要(缺省生成器):事实拼装(轮次意图 + 工具统计 + 收口状态),禁估算禁 LLM 依赖 */
80
+ export function defaultSummarizer(events) {
81
+ const cut = (v, n) => {
82
+ const s = typeof v === 'string' ? v : JSON.stringify(v);
83
+ return s.length > n ? s.slice(0, n) + '…' : s;
84
+ };
85
+ const lines = [`[会话压缩摘要] 折叠事件 ${events.length} 条(${events[0]?.seq ?? 0}–${events.at(-1)?.seq ?? 0}):`];
86
+ const toolCounts = new Map();
87
+ const endReasons = new Map();
88
+ for (const e of events) {
89
+ if (e.kind === 'user')
90
+ lines.push(`- [${e.turnId}] 用户:${cut(e.payload, 200)}`);
91
+ else if (e.kind === 'assistant' && typeof e.payload === 'string' && e.payload.trim())
92
+ lines.push(`- [${e.turnId}] 助手:${cut(e.payload, 160)}`);
93
+ else if (e.kind === 'toolCall') {
94
+ const p = e.payload;
95
+ const name = typeof p?.tool === 'string' ? p.tool : '?';
96
+ toolCounts.set(name, (toolCounts.get(name) ?? 0) + 1);
97
+ }
98
+ else if (e.kind === 'turnEnd') {
99
+ const r = e.meta?.reason;
100
+ const key = typeof r === 'string' ? r : 'unknown';
101
+ endReasons.set(key, (endReasons.get(key) ?? 0) + 1);
102
+ }
103
+ }
104
+ if (toolCounts.size) {
105
+ lines.push(`工具调用:${[...toolCounts.entries()].map(([t, n]) => `${t} ×${n}`).join('、')}`);
106
+ }
107
+ if (endReasons.size) {
108
+ lines.push(`turn 收口:${[...endReasons.entries()].map(([r, n]) => `${r} ×${n}`).join('、')}`);
109
+ }
110
+ return lines.join('\n');
111
+ }
112
+ /**
113
+ * applyCompaction:执行压缩(唯一落链动作 = append 一条 'compact' 事件——原事件链不改写)。
114
+ * summarize 缝:宿主可注入 LLM 摘要(返回非空字符串;缺省确定性生成器)。
115
+ */
116
+ export function applyCompaction(log, opts = {}) {
117
+ const plan = planCompaction(log, opts.protectLastTurns ?? 4);
118
+ const summary = opts.summarize ? opts.summarize(plan.events) : defaultSummarizer(plan.events);
119
+ if (typeof summary !== 'string' || !summary.trim().length) {
120
+ throw new Error(`CAR-E-COMPACT-SUMMARY: 摘要生成产物为空(fail-visible,禁静默折叠空摘要)`);
121
+ }
122
+ return log.append('runtime', 'compact', opts.turnId ?? 'compaction', {
123
+ summary,
124
+ compactedUpTo: plan.compactedUpTo,
125
+ compactedTurns: plan.compactedTurns,
126
+ compactedEvents: plan.events.length,
127
+ });
128
+ }
@@ -1,4 +1,4 @@
1
- export type EventKind = 'user' | 'assistant' | 'toolCall' | 'toolResult' | 'turnEnd' | 'goalUpdate' | 'hostRaw' | 'fork' | 'system' | 'usage';
1
+ export type EventKind = 'user' | 'assistant' | 'toolCall' | 'toolResult' | 'turnEnd' | 'goalUpdate' | 'hostRaw' | 'fork' | 'system' | 'usage' | 'compact' | 'routing';
2
2
  export type Actor = 'user' | 'model' | 'plugin' | 'runtime';
3
3
  export interface SessionEvent {
4
4
  seq: number;
@@ -41,6 +41,10 @@ export declare class SessionLog {
41
41
  * 模型可见流只保留最终结果(真实 provider 对重复 tool_call_id/tool_use_id 拒绝或未定义,
42
42
  * 1.4 观察项经真实适配器落地升级为缺陷面)。id 缺席不收敛(hostRaw/无 id 载荷形态不变,
43
43
  * golden ④ 投影语义兼容);授权拒绝单条即唯一结果原样保留。适配器侧零改动(消费已收敛上游)。
44
+ * 1.14-S2(K-MP-2 / D-40):compact 事件投影——scoped 内最后一条 compact 生效(last-wins),
45
+ * 其 compactedUpTo 前缀折叠为单条 system 摘要(事件原文不改写,仅投影层折叠);compact 之前的
46
+ * 请求快照(atSeq ≤ compact.seq)不含 compact 事件 → 投影仍为全量形态(N1 不变量自洽——
47
+ * 「请求时发出 = 日志前缀投影」逐快照成立);无 compact 事件的日志投影与 1.13 逐字节一致。
44
48
  */
45
49
  deriveMessages(upTo?: number): Array<{
46
50
  role: string;
@@ -64,11 +64,29 @@ export class SessionLog {
64
64
  * 模型可见流只保留最终结果(真实 provider 对重复 tool_call_id/tool_use_id 拒绝或未定义,
65
65
  * 1.4 观察项经真实适配器落地升级为缺陷面)。id 缺席不收敛(hostRaw/无 id 载荷形态不变,
66
66
  * golden ④ 投影语义兼容);授权拒绝单条即唯一结果原样保留。适配器侧零改动(消费已收敛上游)。
67
+ * 1.14-S2(K-MP-2 / D-40):compact 事件投影——scoped 内最后一条 compact 生效(last-wins),
68
+ * 其 compactedUpTo 前缀折叠为单条 system 摘要(事件原文不改写,仅投影层折叠);compact 之前的
69
+ * 请求快照(atSeq ≤ compact.seq)不含 compact 事件 → 投影仍为全量形态(N1 不变量自洽——
70
+ * 「请求时发出 = 日志前缀投影」逐快照成立);无 compact 事件的日志投影与 1.13 逐字节一致。
67
71
  */
68
72
  deriveMessages(upTo) {
69
73
  const scoped = upTo !== undefined ? this.events.slice(0, upTo) : this.events;
70
- const lastSeqById = new Map();
74
+ let compactEvent;
71
75
  for (const e of scoped) {
76
+ if (e.kind === 'compact')
77
+ compactEvent = e;
78
+ } // last-wins
79
+ let compactedUpTo;
80
+ if (compactEvent) {
81
+ const cp = (compactEvent.payload ?? {});
82
+ if (typeof cp.summary !== 'string' || !cp.summary.length || typeof cp.compactedUpTo !== 'number') {
83
+ throw new Error(`CAR-E-COMPACT-PAYLOAD: compact 事件 seq=${compactEvent.seq} 缺有效 summary/compactedUpTo(投影拒绝——fail-visible)`);
84
+ }
85
+ compactedUpTo = cp.compactedUpTo; // 折叠边界 = payload 声明的前缀边界(非 compact 事件自身 seq)
86
+ }
87
+ const projected = compactedUpTo !== undefined ? scoped.filter(e => e.seq >= compactedUpTo) : scoped;
88
+ const lastSeqById = new Map();
89
+ for (const e of projected) {
72
90
  if (e.kind !== 'toolResult')
73
91
  continue;
74
92
  const id = e.payload?.id;
@@ -76,7 +94,9 @@ export class SessionLog {
76
94
  lastSeqById.set(id, e.seq);
77
95
  }
78
96
  const out = [];
79
- for (const e of scoped) {
97
+ if (compactEvent)
98
+ out.push({ role: 'system', content: (compactEvent.payload.summary) });
99
+ for (const e of projected) {
80
100
  if (e.kind === 'system')
81
101
  out.push({ role: 'system', content: e.payload });
82
102
  else if (e.kind === 'user')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lqc123qwe/car-runtime",
3
- "version": "1.13.0",
3
+ "version": "1.14.1",
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
@@ -25,6 +25,7 @@ import { Context } from './kernel/context.ts'
25
25
  import { mountPlugin } from './load/loader.ts'
26
26
  import { verifyPluginFile } from './load/sigGate.ts'
27
27
  import { loadCarConfig, mergeSignatureGate, mergeLlmConfig, warmCarConfig } from './load/config.ts'
28
+ import { assembleModelPool } from './load/pool.ts'
28
29
  import { generateSigningKeypair, signPluginFile, resolveTrustRoot } from './load/sign.ts'
29
30
  import { doctorCredentials, doctorConnectivity, doctorKeychain, doctorSignature, doctorModelReadiness } from './dx/doctor.ts'
30
31
  import { RuntimeCore, createOpenAICompatAdapter, createAnthropicAdapter } from './runtime-core/llm.ts'
@@ -64,8 +65,9 @@ const HELP = `用法: car <command> [args]
64
65
  CAR_UNSIGNED_ALLOW=1 显式声明豁免(warn 缺签路径计数 confirmed=yes,横幅保留)
65
66
  配置文件通道(1.5 起双载体):cwd car.config.ts(export default {…};发现序 ts > json)或
66
67
  car.config.json 或 --config <path>;键位 sandbox.unsigned.allow / sandbox.sig.enforce /
67
- sandbox.sig.trustRoot / llm.* / mcp.servers.*(stdio: command/args/env;远程: url/headers,
68
- \${ENV_VAR} 引用注入凭据);优先级 flag > env > 配置 > 缺省;未知键 fail-visible`
68
+ sandbox.sig.trustRoot / llm.* / llm.modelPool.*(多端点 fallback:endpoints id/baseUrl/model/
69
+ adapterId/keyEnv/priority/weight/maxTokens + probe + maxAttempts;路由决策落链)/ mcp.servers.*
70
+ (stdio: command/args/env;远程: url/headers,\${ENV_VAR} 引用注入凭据);优先级 flag > env > 配置 > 缺省;未知键 fail-visible`
69
71
 
70
72
  // 进程内热重载会话(ReloadManager 持有 epoch 与实例注册表;同进程连续 reload 语义完整)
71
73
  let reloadMgrPromise: Promise<import('./load/report.ts').ReloadManager> | undefined
@@ -193,27 +195,55 @@ async function main(): Promise<number> {
193
195
  ...(mtFlag !== undefined ? { maxTokens: Number(mtFlag) } : {}),
194
196
  })
195
197
  const prompt = flagValue(rest, '--prompt')
196
- if (!llm.baseUrl || !llm.model || prompt === undefined) {
197
- console.error('CAR-E-LLM-CONFIG: 真实模型路径需要 baseUrl + model + --prompt——')
198
- console.error(' 配置:car.config.ts(export default)或 car.config.json {"llm":{"baseUrl":"https://…","model":"…"}} / env CAR_LLM_BASE_URL+CAR_LLM_MODEL / flag --base-url --model --prompt')
199
- console.error(' anthropic:llm.adapterId "anthropic"(或 --adapter-id anthropic)+ baseUrl https://api.anthropic.com;凭据 ANTHROPIC_API_KEY')
200
- console.error(' 凭据:OPENAI_API_KEY / ANTHROPIC_API_KEY(env fallback 需 CAR_ALLOW_ENV_CREDENTIALS=1 或 llm.allowEnvFallback)或 keychain;诊断:car doctor')
198
+ if (prompt === undefined) {
199
+ console.error('CAR-E-LLM-CONFIG: 真实模型路径需要 --prompt——')
201
200
  console.error(' (无模型演示流:--demo)')
202
201
  return 2
203
202
  }
204
- const modelName = llm.model
203
+ // 1.14-S4(K-POOL-1 / D-42):modelPool 登记时池装配压过 llm.baseUrl/model/adapterId(每次路由决策落链可解释)
204
+ const poolCfg = cfgLlm.config.modelPool ?? null
205
+ const cfgBaseUrl = llm.baseUrl
206
+ const cfgModel = llm.model
207
+ let modelName = cfgModel ?? ''
205
208
  const credService = new CredentialService()
206
209
  const core = new RuntimeCore(credService)
207
210
  core.bindContext(ctx)
208
211
  // 适配器经根作用域 effect 可逆注册(bindContext 必须先于 register——否则走非可逆直注册分支);
209
- // 1.5-S1(D-20):adapterId='anthropic' → anthropic 适配器(Messages API);缺省 openai-compat 不变
210
- core.registerLlmAdapter(llm.adapterId === 'anthropic'
211
- ? createAnthropicAdapter({ baseUrl: llm.baseUrl, credentials: credService, provider: 'anthropic' })
212
- : createOpenAICompatAdapter({
213
- baseUrl: llm.baseUrl,
214
- credentials: credService,
215
- provider: 'openai-compat',
216
- }), { default: true })
212
+ // 1.5-S1(D-20):adapterId='anthropic' → anthropic 适配器(Messages API);缺省 openai-compat 不变;
213
+ // 1.14-S4(K-POOL-1 / D-42):modelPool 登记时池装配压过单端点(onDecide → routing/decide 落链)
214
+ if (poolCfg) {
215
+ const assembly = assembleModelPool({
216
+ pool: poolCfg,
217
+ credentials: credService,
218
+ onDecide: d => log.append('runtime', 'routing', d.turnId ?? 'T0', {
219
+ type: 'decide',
220
+ selected: d.selected,
221
+ reason: d.reason,
222
+ retries: d.retries,
223
+ attempts: d.attempts,
224
+ }),
225
+ })
226
+ core.registerLlmAdapter(assembly.adapter, { default: true })
227
+ // chatStep model 形参 = 首位候选(池内逐成员改写 req.model——usage 按成员模型落账)
228
+ modelName = assembly.members[0]!.model
229
+ console.log(`[pool] ${assembly.members.length} 端点入池(候选序:${assembly.members.map(m => m.id).join(' → ')})`)
230
+ } else {
231
+ if (!cfgBaseUrl || !cfgModel) {
232
+ console.error('CAR-E-LLM-CONFIG: 真实模型路径需要 baseUrl + model——')
233
+ console.error(' 配置:car.config.ts(export default)或 car.config.json {"llm":{"baseUrl":"https://…","model":"…"}} / env CAR_LLM_BASE_URL+CAR_LLM_MODEL / flag --base-url --model')
234
+ console.error(' anthropic:llm.adapterId "anthropic"(或 --adapter-id anthropic)+ baseUrl https://api.anthropic.com;凭据 ANTHROPIC_API_KEY')
235
+ console.error(' 模型池:llm.modelPool.endpoints(多端点 fallback + health 预检;凭据 keyEnv 引用;路由决策落链)')
236
+ console.error(' 凭据:OPENAI_API_KEY / ANTHROPIC_API_KEY(env fallback 需 CAR_ALLOW_ENV_CREDENTIALS=1 或 llm.allowEnvFallback)或 keychain;诊断:car doctor')
237
+ return 2
238
+ }
239
+ core.registerLlmAdapter(llm.adapterId === 'anthropic'
240
+ ? createAnthropicAdapter({ baseUrl: cfgBaseUrl, credentials: credService, provider: 'anthropic' })
241
+ : createOpenAICompatAdapter({
242
+ baseUrl: cfgBaseUrl,
243
+ credentials: credService,
244
+ provider: 'openai-compat',
245
+ }), { default: true })
246
+ }
217
247
  // MCP 工具进 turn(D-18):--mcp <serverId>(可重复;server 启动规格在 car.config.json mcp.servers 登记)
218
248
  const mcpIds: string[] = []
219
249
  // 1.8-D29:活跃 turn span 句柄(startTurn 后赋值——MCP 工具闭包运行期取 client span 挂链面)
@@ -302,7 +332,8 @@ async function main(): Promise<number> {
302
332
  model: modelName,
303
333
  tools: toToolDefinitions(toolsMap),
304
334
  ...(llm.maxTokens !== undefined ? { maxTokens: llm.maxTokens } : {}),
305
- ...(llm.adapterId ? { adapterId: llm.adapterId } : {}),
335
+ // 1.14-S4:池装配时不传 adapterId(池适配器 = default;req.model 由池内按成员改写)
336
+ ...(poolCfg ? {} : llm.adapterId ? { adapterId: llm.adapterId } : {}),
306
337
  signal,
307
338
  }).then(
308
339
  res => { step.end(res.stopReason); return res },