@lqc123qwe/car-runtime 1.4.0 → 1.6.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/.github/workflows/ci.yml +12 -0
- package/README.md +31 -2
- package/package.json +5 -1
- package/scripts/release-pipeline.ts +19 -2
- package/src/cli.ts +43 -21
- package/src/dx/doctor.ts +9 -7
- package/src/host/facade.ts +44 -7
- package/src/host/hostGateway.ts +2 -1
- package/src/host/stdio.ts +11 -4
- package/src/load/config.ts +210 -137
- package/src/mcp/httpClient.ts +176 -0
- package/src/runtime-core/chatStep.ts +9 -3
- package/src/runtime-core/credentials.ts +6 -1
- package/src/runtime-core/llm.ts +289 -75
- package/src/session/log.ts +22 -4
- package/test/s33-anthropic-adapter.spec.ts +354 -0
- package/test/s34-mcp-remote-transport.spec.ts +250 -0
- package/test/s35-config-ts-zod.spec.ts +145 -0
- package/test/s36-host-tool-surface.spec.ts +250 -0
- package/test/s37-projection-dedupe.spec.ts +161 -0
- package/test/s38-config-jiti.spec.ts +116 -0
package/.github/workflows/ci.yml
CHANGED
|
@@ -66,6 +66,8 @@ jobs:
|
|
|
66
66
|
- uses: actions/checkout@v4
|
|
67
67
|
- uses: actions/setup-node@v4
|
|
68
68
|
with: { node-version: '22.19' }
|
|
69
|
+
# 1.5 起 zod 为常规运行时依赖(D-22)——CLI/doctor 传递加载,测试 job 需 node_modules
|
|
70
|
+
- run: npm install
|
|
69
71
|
- run: node -p "'G-06 node ' + process.version"
|
|
70
72
|
- run: node --experimental-sqlite --experimental-transform-types --test "test/*.spec.ts"
|
|
71
73
|
|
|
@@ -77,6 +79,8 @@ jobs:
|
|
|
77
79
|
- uses: actions/checkout@v4
|
|
78
80
|
- uses: actions/setup-node@v4
|
|
79
81
|
with: { node-version: '22.19' }
|
|
82
|
+
# 1.5 起 zod 为常规运行时依赖(D-22)——CLI/doctor 传递加载,测试 job 需 node_modules
|
|
83
|
+
- run: npm install
|
|
80
84
|
- run: node -p "'G-06 node ' + process.version"
|
|
81
85
|
- run: node --experimental-sqlite --experimental-transform-types --test "test/*.spec.ts"
|
|
82
86
|
|
|
@@ -88,6 +92,8 @@ jobs:
|
|
|
88
92
|
- uses: actions/checkout@v4
|
|
89
93
|
- uses: actions/setup-node@v4
|
|
90
94
|
with: { node-version: '22.19' }
|
|
95
|
+
# 1.5 起 zod 为常规运行时依赖(D-22)——CLI/doctor 传递加载,测试 job 需 node_modules
|
|
96
|
+
- run: npm install
|
|
91
97
|
- run: node -p "'G-06 node ' + process.version"
|
|
92
98
|
- run: node --experimental-sqlite --experimental-transform-types --test "test/*.spec.ts"
|
|
93
99
|
|
|
@@ -163,6 +169,8 @@ jobs:
|
|
|
163
169
|
- uses: actions/checkout@v4
|
|
164
170
|
- uses: actions/setup-node@v4
|
|
165
171
|
with: { node-version: '22.19' }
|
|
172
|
+
# 1.5 起 zod 为常规运行时依赖(D-22)——cli.ts 传递加载,dx-smoke 需 node_modules
|
|
173
|
+
- run: npm install
|
|
166
174
|
- run: node --experimental-transform-types src/cli.ts doctor
|
|
167
175
|
- run: node --experimental-transform-types src/cli.ts reload test/fixtures/hello-plugin.ts
|
|
168
176
|
|
|
@@ -174,6 +182,8 @@ jobs:
|
|
|
174
182
|
- uses: actions/checkout@v4
|
|
175
183
|
- uses: actions/setup-node@v4
|
|
176
184
|
with: { node-version: '22.19' }
|
|
185
|
+
# 1.5 起 zod 为常规运行时依赖(D-22)——cli.ts 传递加载,dx-smoke 需 node_modules
|
|
186
|
+
- run: npm install
|
|
177
187
|
- run: node --experimental-transform-types src/cli.ts doctor
|
|
178
188
|
- run: node --experimental-transform-types src/cli.ts reload test/fixtures/hello-plugin.ts
|
|
179
189
|
|
|
@@ -185,6 +195,8 @@ jobs:
|
|
|
185
195
|
- uses: actions/checkout@v4
|
|
186
196
|
- uses: actions/setup-node@v4
|
|
187
197
|
with: { node-version: '22.19' }
|
|
198
|
+
# 1.5 起 zod 为常规运行时依赖(D-22)——cli.ts 传递加载,dx-smoke 需 node_modules
|
|
199
|
+
- run: npm install
|
|
188
200
|
- run: node --experimental-transform-types src/cli.ts doctor
|
|
189
201
|
- run: node --experimental-transform-types src/cli.ts reload test/fixtures/hello-plugin.ts
|
|
190
202
|
|
package/README.md
CHANGED
|
@@ -5,7 +5,8 @@ Composable Agent Runtime — S1 迭代产物:微内核骨架(F1 可逆注册
|
|
|
5
5
|
## 运行
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
#
|
|
8
|
+
npm install # 1.5 起 zod 为常规运行时依赖(D-22)——CLI/doctor 传递加载
|
|
9
|
+
# 测试(Node 22.19+;sqlite 需 --experimental-sqlite flag)
|
|
9
10
|
node --experimental-transform-types --test test/*.spec.ts
|
|
10
11
|
```
|
|
11
12
|
|
|
@@ -184,9 +185,37 @@ node --experimental-transform-types --test test/*.spec.ts
|
|
|
184
185
|
- **1.3 发布资产自动化(W4,2026-09-27 收口)**:`scripts/release-artifacts.ts` 制品构建单一事实源(本地流水线/CI j16 双端复用;SHA256SUMS 裸字节自检 G-11 口径 + SBOM G-05 同构 + releaseAssetNames 五件套清单——G-10/CI/测试三方同源)+ release.yml 重构(构建 → cosign keyless 签名〔D-15 正式轨:OIDC+Rekor,CI 零 keypair 材料〕→ `gh release upload --clobber` 五件套 → npm publish 原步不动;**upload 先于 publish**)+ D-14 资产集迁移(car-release.pub 移出——keyless 验证锚 = bundle 证书 + Rekor)+ D-14 tag 形态统一(裸版本号)。s30 七测(构建/清单/同源/yml 结构与步骤序红线/**name 行冒号守卫**〔1.3-BUG-2 回归钉〕)。全量 **270(267 PASS + 3 skip / 0 fail)**。W1 三度延后登记 **1.4 头号候选**(设计账本 D-12a loopback 豁免 + D-13 --demo 保留)。规划:`car-docs/10-内核v1/1.3-迭代规划.md`;收口报告:`car-docs/10-内核v1/1.3-收口终验报告.md`。
|
|
185
186
|
- **1.3.0 正式发布(2026-09-27,S5 收官,W4 全自动首战兑现)**:Release `1.3.0`(tag 32dad29)→ **j16 run 36320604746 全步 success**(构建 → keyless 签名 → 自动上传五件套〔零人工拖拽〕→ npm publish)→ npm `@lqc123qwe/car-runtime@1.3.0` 在架,**dist-tags latest → 1.3.0、rc 保留 1.0.0-rc.1** + provenance。**post-publish 12 PASS / 1 DRY-RUN / 0 FAIL(G-09 实查)**;外部等价检查 **SHA256 MATCH(`70767c8d…`)+ cosign keyless Verified OK**(证书身份 + Rekor——D-15 正式轨兑现,pub 退役)。发布工程事件:首发受挫 1.3-BUG-2(workflow 步骤名未引号冒号 → YAML 解析失败 → startup failure 且 release 事件静默失效;**改 workflow 必须本地 PyYAML 复验**,post-publish 必须核对 release 事件 run 真实触发)。
|
|
186
187
|
|
|
188
|
+
## 1.4 状态(真实模型接入 W1 完整)
|
|
189
|
+
|
|
190
|
+
| `src/cli.ts` | 1.4-S5(D-13)car run 双路径:真路径(Context+RuntimeCore+bindContext→registerLlmAdapter 根作用域 effect → runTurn model 回调=chatStep;SIGINT 取消;SIGINT→signal)+ `--demo` 保留 stub(demo_tool 撞名边缘出清);无模型配置 exit 2 显式引导(CAR-E-LLM-CONFIG) | M8 §1「runTurn 消费接线」出清 |
|
|
191
|
+
| `src/load/config.ts` +2 | 1.4-S3(D-16)`llm.*` 键位(baseUrl/model/adapterId/maxTokens/allowEnvFallback——fail-visible)+ `mcp.servers.*` 键位(D-18;env 禁明文凭据由 gateway 强制检查)+ mergeLlmConfig(flag > env > config) | 1.4 W1-2 |
|
|
192
|
+
| `src/runtime-core/tools.ts` | 1.4-S1 toToolDefinitions(执行面 Map → 模型声明面;T-22 缺省 write 收口;执行面=声明面同一对象红线) | 1.4 W1-3 |
|
|
193
|
+
| `src/load/loader.ts` + `src/loop/stop.ts` | 1.4-S1 ToolReg/ToolDef 加 description/parameters/declaredSideEffect(加法);PluginApi.setSystemPrompt(pending 收集 → bindCore 冲刷,last-wins) | 1.4 W1-3/W1-4 |
|
|
194
|
+
| `src/session/log.ts` + `src/runtime-core/chatStep.ts` | 1.4-S2(D-17)EventKind 加 'system' + deriveMessages 投影 role:system + chatStep 角色映射 + golden 登记(contract-check 4/4);1.4-S4(D-19)**TurnAborted 全库首次实抛**(chunk 间隙 + finish 检查前序——assistant 未落链无半截) | §3.5.5 红线兑现 |
|
|
195
|
+
| `src/runtime-core/llm.ts` + `types.ts` | 1.4-S4:D-12a TLS loopback 豁免(http 仅 127.0.0.1/localhost/[::1]——1.4-BUG-3:URL.hostname IPv6 带方括号)+ LlmRequest.signal 透传三检查点(取消 ≠ 失败:不 retry 不 error chunk);**1.4-BUG-1 修复:出站 fetch 缺 method:'POST'(M8 潜伏——注入式 fetch 单测忽略 method,真 undici 暴露)** | 1.4 W1-5/W1-7 |
|
|
196
|
+
| `src/runtime-core/telemetry.ts` + `trace.ts` | 1.4-S6:startSpan trace 透传(traceId 复用 + parentSpanId)+ TelemetrySpan.spanId + TurnTracer(turn 根 → step 子层级;**挂点装配层**——loop 层零改动微调登记);**1.4-BUG-2 修复:出站默认 node:http(s)(win32 全局 fetch/undici + 进程退出 → libuv 断言崩溃 0xC0000409,Node 23.5 实测/22 不复现)** | M8 §4.4 遥测未入链出清 |
|
|
197
|
+
| `src/mcp/stdioClient.ts` + `src/ptc/runCode.ts` +2 | 1.4-S7(D-18)MCP stdio 客户端传输(spawn + JSON-RPC 行协议 + 30s 超时)+ `--mcp <serverId>` → gateway → 模型名 `mcp_<serverId>_<name>` + callTool 桥;makePtcToolDefinition 补 parameters + `--ptc` 桥(声明面=授权面同源快照)+ **renderSdkFromRegistry 首次生产调用点**(SDK 声明块独立 system 事件) | 1.4 MCP/PTC 块 |
|
|
198
|
+
| `test/s31-real-model-wiring.spec.ts` + `test/s32-telemetry-mcp-ptc.spec.ts` | 1.4 测试:s31 9 测(声明面/system/配置/loopback/取消/CarM8Error/多步真 E2E/spawn 全链/无配置 exit 2)+ s32 3 测(trace 透传/TurnTracer/**car run 全链:MCP 真进程+PTC worker 桥+SDK 出站+trace 父子链**) | 1.4-GO |
|
|
199
|
+
|
|
200
|
+
- **1.4 工程收口(2026-09-27)**:W1 完整四块兑现——**car run 首次真实调模型**(多步工具循环 E2E 全链),模型配置通道(llm.* + env + flags)、system prompt 落链(§3.5.5 红线兑现)、TurnAborted 首次实抛、CarM8Error 保形(A080001 引导文案可达)、TLS loopback 豁免(D-12a 兑现)、MCP/PTC 进真实 turn、遥测 turn/step span 出站。全量 **282(279 PASS + 3 能力门控 skip / 0 fail)** + tsc strict 0 + secrets-scan 105 文件 0 命中。**1.4-BUG-1(fetch 缺 POST)/BUG-2(win32 退出崩溃)/BUG-3(IPv6 方括号)均为生产接线暴露的真缺陷**。
|
|
201
|
+
- **1.4.0 正式发布(2026-09-27,S9 收官,全自动第二次实战)**:npm `@lqc123qwe/car-runtime@1.4.0` 在架——**dist-tags latest → 1.4.0、rc 保留指向 1.0.0-rc.1** + provenance。Release `1.4.0`(agent 经 GCM 凭据 API 创建)→ **j16 run 36328295548 全步 success**(构建/keyless 签名/自动上传五件套/npm publish)→ **post-publish 12 PASS / 1 DRY-RUN / 0 FAIL(G-09 实查)**;外部等价检查 **SHA256 MATCH(`55ce08fd…`)+ cosign keyless Verified OK**。
|
|
202
|
+
- **遗留登记**:anthropic 适配器(冻结);远程 MCP 传输(SSE/HTTP);写类工具双 toolResult 投影观察项;car.config.ts+zod;jiti。规划:`car-docs/10-内核v1/1.4-迭代规划.md`;收口报告:`car-docs/10-内核v1/1.4-收口终验报告.md`。
|
|
203
|
+
|
|
204
|
+
## 1.5 状态(provider 扩展 + 远程 MCP + 配置完整形态)
|
|
205
|
+
|
|
206
|
+
| `src/runtime-core/llm.ts` | 1.5-S1(D-20)anthropic 适配器:Messages API 流式 SSE(data-only 解析;`{baseUrl}/v1/messages`;x-api-key + anthropic-version 2023-06-01;system 顶层化〔多条 \n\n 连接〕;toolResult 收敛单 user 多 tool_result 块〔is_error〕;tools → input_schema;**max_tokens 必填缺省 4096**;stop_reason 映射未映射 fail-visible;凭据 provider='anthropic')+ **streamSsePost 引擎抽取**(§3.5.4 重试/超时/取消语义单点共用——openai-compat/anthropic 不漂移)+ assertTlsOrLoopback 抽取(D-12a 口径不变) | 1.4 冻结表 anthropic 出清 |
|
|
207
|
+
| `src/runtime-core/chatStep.ts` | **1.5-BUG-1 修复:toolCall 投影丢弃**——deriveMessages 的 assistant 工具调用形态为 `{role:'assistant', toolCall}`(log.ts 口径),chatStep 仅透传 content → 工具调用历史在后续请求静默消失(真实 provider 对无 tool_calls 前驱的 tool 消息 400);anthropic spawn E2E 生产调用点核查暴露,openai-compat 同受益 | 1.5-GO-5 双向核查产出 |
|
|
208
|
+
| `src/mcp/httpClient.ts` | 1.5-S2(D-21)远程 MCP 传输:Streamable HTTP(2025-06-18 单轨)+ **懒 initialize 握手**(恰好一次;Mcp-Session-Id 捕获回传)+ JSON/SSE 双形态响应(SSE 命中即收口流显式 destroy)+ **出站 node:http(s) 不走 undici fetch**(1.4-BUG-2 已验证口径;本地 spawn E2E 曾复现 win32 退出 0xC0000409,换通道后消除)+ TLS 门复用 + resolveMcpHeaders(`${ENV_VAR}` 值内插值 + 字面明文凭据拒绝 CR-05) | 1.4 冻结表远程 MCP 出清 |
|
|
209
|
+
| `src/load/config.ts` | 1.5-S3(D-22)配置完整形态:**car.config.ts 载体**(动态 import 与插件加载同纪律;发现序 ts > json〔并存 ts 胜出〕;warmCarConfig 预热保持 loadCarConfig 同步签名——未预热显式报错禁静默回退 json)+ **zod ^3 校验层**(首个常规运行时依赖,D-11a ① 修订;CAR-E-CONFIG fail-visible 保持;issue 渲染 `路径 原因`;car.config.json 键位兼容)+ mcp.servers url/headers 键位(command/url 互斥恰好其一;通道专属键 fail-visible) | 1.1-S3「届时迁移」预登记出清 |
|
|
210
|
+
| `src/dx/doctor.ts` + `src/runtime-core/credentials.ts` + `src/cli.ts` | 1.5-S4 配套:doctorModelReadiness provider 感知(adapterId anthropic → ANTHROPIC_API_KEY;providerEnvVars 导出复用)+ `--adapter-id anthropic` 装配分支 + HELP/错误文案双载体与 anthropic 引导 | 1.5 DX 配套 |
|
|
211
|
+
| `test/s33~s35` | s33 8 测(出站体/stop_reason 映射/流容错/chatStep 多块聚合/基线同款 TLS·凭据·重试·取消/doctor 感知/spawn E2E)+ s34 6 测(握手序/会话头/SSE 形态/closed 拒绝/headers 凭据规则/config 键位/spawn `--mcp` url 全链)+ s35 4 测(zod 键位兼容/发现序/预热协议+epoch 击穿/TS 形态校验) | 1.5-GO |
|
|
212
|
+
|
|
213
|
+
- **1.5 工程收口(2026-09-28,`ec5a4c4`)**:三大登记项全部兑现(1.4 冻结表「登记后续」清零)。全量 **300(297 PASS + 3 能力门控 skip / 0 fail)** + tsc strict 0 + secrets-scan commit 后复跑 114 文件 0 命中 + contract-check 4/4(golden 不动)。**1.5-BUG-1 为生产调用点核查直接产出**(1.4 的 E2E 假服务不校验 tool_calls 历史故未暴露)。**1.5-插曲-1(HELP 模板串 `${ENV_VAR}` 未转义 → CLI 全 spawn 挂)**——模板字面量内插值转义教训。**1.5-CI-1:test/dx-smoke jobs 补 npm install**(零依赖 CI 前提随 D-22 失效——j02/j03/j04/j10/j11/j12;改 workflow 后 PyYAML 复验执行)。
|
|
214
|
+
- **1.5.0 正式发布(2026-09-28,S6 收官)**:npm `@lqc123qwe/car-runtime@1.5.0` 在架——**dist-tags latest → 1.5.0、rc 保留 1.0.0-rc.1** + provenance(slsa.dev/provenance/v1)。Release `1.5.0`(agent GCM 凭据 API 创建)→ **j16 run 36394117668 全步 success**(构建/keyless 签名/自动上传五件套/npm publish)→ **post-publish 12 PASS / 1 DRY-RUN / 0 FAIL(G-09 实查)**;外部等价检查 **SHA256 MATCH(`2edae513…`)+ Rekor 透明日志交叉验证**(logIndex 2981593515,bundle↔API 双端一致;cosign 二进制本地缺席,以 Rekor API 对拍替代——登记口径)。**G-04 门禁口径随 D-22 更新**:零依赖断言 → npm audit(omit=dev high,0 findings)。
|
|
215
|
+
- **遗留登记**:jiti 首载;host 工具面;写类工具双 toolResult 投影观察项;MCP legacy HTTP+SSE(2024-11-05)不追溯。规划:`car-docs/10-内核v1/1.5-迭代规划.md`;收口报告:`car-docs/10-内核v1/1.5-收口终验报告.md`。
|
|
187
216
|
## 状态
|
|
188
217
|
|
|
189
|
-
- **版本主线**:S1-S4 48/48 | M2 89/89(v0.2.0)| M3 127/127(v0.3.0)| M4 139/139(**1.0.0-rc.1 已发布**)| M5 156/156(DEC-4 预埋项转正)| M6 179/179(2026-09-15 收口)| M7 全量 195(192 PASS + 3 能力门控 skip,2026-09-19 收口)| **M5-S28 装载签名门 全量 204(201 PASS + 3 skip,2026-09-26)** | **M8 运行时底座 全量 231(228 PASS + 3 skip,2026-09-26 收口,§3.2 八模块全部收口)** | **1.1 签名收尾最小集 全量 249(246 PASS + 3 skip,2026-09-27 收口,1.1.0 已发布,enforce 翻转准备就绪)** | **1.2 会话/遥测小项 全量 263(260 PASS + 3 skip,2026-09-27 收口,1.2.0 已发布)** | **1.3 发布资产自动化 全量 270(267 PASS + 3 skip,2026-09-27 收口,1.3.0 已发布,全链零人工拖拽兑现)**
|
|
218
|
+
- **版本主线**:S1-S4 48/48 | M2 89/89(v0.2.0)| M3 127/127(v0.3.0)| M4 139/139(**1.0.0-rc.1 已发布**)| M5 156/156(DEC-4 预埋项转正)| M6 179/179(2026-09-15 收口)| M7 全量 195(192 PASS + 3 能力门控 skip,2026-09-19 收口)| **M5-S28 装载签名门 全量 204(201 PASS + 3 skip,2026-09-26)** | **M8 运行时底座 全量 231(228 PASS + 3 skip,2026-09-26 收口,§3.2 八模块全部收口)** | **1.1 签名收尾最小集 全量 249(246 PASS + 3 skip,2026-09-27 收口,1.1.0 已发布,enforce 翻转准备就绪)** | **1.2 会话/遥测小项 全量 263(260 PASS + 3 skip,2026-09-27 收口,1.2.0 已发布)** | **1.3 发布资产自动化 全量 270(267 PASS + 3 skip,2026-09-27 收口,1.3.0 已发布,全链零人工拖拽兑现)** | **1.4 真实模型接入 全量 282(279 PASS + 3 skip,2026-09-27 收口,1.4.0 已发布)** | **1.5 provider 扩展+远程 MCP+配置完整形态 全量 300(297 PASS + 3 skip,2026-09-28 收口,1.5.0 已发布)**
|
|
190
219
|
- **1.0.0 正式发布(2026-09-26,S29/S30)**:npm `@lqc123qwe/car-runtime@1.0.0` 在架——**dist-tag latest → 1.0.0,rc 保留指向 1.0.0-rc.1**(§1.1 口径);SLSA provenance(Actions OIDC,透明日志在案);GitHub Release v1.0.0 六资产(tgz/bundle/SHA256SUMS/SBOM/审计/公钥);**post-publish 全链 13 PASS / 0 DRY-RUN / 0 FAIL** + 外部验证者等价检查(Release 实下载件 SHA256 MATCH + cosign Verified OK)。**宽限期时钟:2026-09-26 起算,2026-12-25 到期 verifier 默认值 warn→enforce 翻转(计划内变更)**。终验报告:`car-docs/10-内核v1/M5-S30-1.0收口终验报告.md`。发布工程事件:j16 首跑 E403(npm 2FA 策略 vs granular token 无 bypass——清单预设回退路径兑现)→ bypass token re-run 绿;G-09 期望 tag 动态化(`4c84b17`)在真实发布兑现价值。
|
|
191
220
|
- **1.0.0-rc.1 已发布(2026-09-09)**:npm `@lqc123qwe/car-runtime@1.0.0-rc.1`(rc + latest 双 tag,SLSA provenance 在案)+ GitHub Release 6 制品;发布预演 **13 PASS / 0 DRY-RUN / 0 FAIL**(c762b72)。S22 GO 清单已全绿收口。
|
|
192
221
|
- **轨道 A 三项全部完成**:E-1 WSL2 逃逸矩阵 **20/20 PASS 零逃逸**(2026-09-07,G-07 转 PASS)| E-3 远端发布通道 **13/13 全门禁 PASS**(2026-09-09,G-08/09/10 转 PASS)| E-2 Windows koffi FFI **四判据双环境全 PASS**(S24/S25/S26,2026-09-09~10;本机非提权 + windows-latest 提权 runner 双绿,CI 门禁已接入)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lqc123qwe/car-runtime",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.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",
|
|
@@ -24,5 +24,9 @@
|
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/node": "^26.6.1"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"jiti": "^2.7.0",
|
|
30
|
+
"zod": "^3.25.76"
|
|
27
31
|
}
|
|
28
32
|
}
|
|
@@ -69,9 +69,26 @@ const record = (stage: string, gate: string, ok: boolean, detail = '', dryRun =
|
|
|
69
69
|
for (const p of secretPatterns) if (p.test(content)) hits++
|
|
70
70
|
}
|
|
71
71
|
record(stage, 'G-03 硬编码密钥扫描 = 0', hits === 0, `${hits} hits(扫描 ${scanned} 个 .ts,豁免:secrets.ts 规则库 + s7 标定基准,豁免清单见脚本注记)`)
|
|
72
|
-
//
|
|
72
|
+
// 依赖审计:1.5-S3(D-22)zod 为首个常规运行时依赖 → 门禁口径从「零依赖断言」切换为
|
|
73
|
+
// npm audit(omit=dev,high 阻塞)——与 ci.yml j01 依赖审计同款;audit 不可达(离线)= DRY-RUN
|
|
73
74
|
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8'))
|
|
74
|
-
|
|
75
|
+
const depCount = Object.keys(pkg.dependencies ?? {}).length
|
|
76
|
+
let auditOk = true
|
|
77
|
+
let auditDetail = '无运行时依赖(audit 免除)'
|
|
78
|
+
if (depCount > 0) {
|
|
79
|
+
if (process.env.REMOTE_CHECK === '1') {
|
|
80
|
+
try {
|
|
81
|
+
execSync('npm audit --omit=dev --audit-level=high', { cwd: ROOT, stdio: 'pipe' })
|
|
82
|
+
auditDetail = `运行时依赖 ${depCount} 项(D-22:zod ^3);npm audit --omit=dev --audit-level=high 0 findings`
|
|
83
|
+
} catch {
|
|
84
|
+
auditOk = false
|
|
85
|
+
auditDetail = `运行时依赖 ${depCount} 项;npm audit --audit-level=high 有 findings(阻塞——G-04 D-22 口径)`
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
auditDetail = `运行时依赖 ${depCount} 项(D-22:zod ^3);npm audit 在 REMOTE_CHECK=1 实跑(DRY-RUN)`
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
record(stage, 'G-04 依赖审计', auditOk, auditDetail)
|
|
75
92
|
}
|
|
76
93
|
|
|
77
94
|
// ── 阶段 2:构建与制品 ──
|
package/src/cli.ts
CHANGED
|
@@ -24,10 +24,10 @@ import { runTurn } from './loop/stop.ts'
|
|
|
24
24
|
import { Context } from './kernel/context.ts'
|
|
25
25
|
import { mountPlugin } from './load/loader.ts'
|
|
26
26
|
import { verifyPluginFile } from './load/sigGate.ts'
|
|
27
|
-
import { loadCarConfig, mergeSignatureGate, mergeLlmConfig } from './load/config.ts'
|
|
27
|
+
import { loadCarConfig, mergeSignatureGate, mergeLlmConfig, warmCarConfig } from './load/config.ts'
|
|
28
28
|
import { generateSigningKeypair, signPluginFile, resolveTrustRoot } from './load/sign.ts'
|
|
29
29
|
import { doctorCredentials, doctorConnectivity, doctorKeychain, doctorSignature, doctorModelReadiness } from './dx/doctor.ts'
|
|
30
|
-
import { RuntimeCore, createOpenAICompatAdapter } from './runtime-core/llm.ts'
|
|
30
|
+
import { RuntimeCore, createOpenAICompatAdapter, createAnthropicAdapter } from './runtime-core/llm.ts'
|
|
31
31
|
import { CredentialService } from './runtime-core/credentials.ts'
|
|
32
32
|
import { chatStep } from './runtime-core/chatStep.ts'
|
|
33
33
|
import { toToolDefinitions } from './runtime-core/tools.ts'
|
|
@@ -36,8 +36,11 @@ import { TurnTracer } from './runtime-core/trace.ts'
|
|
|
36
36
|
import type { CounterName, AllowedLabels } from './telemetry/metrics.ts'
|
|
37
37
|
|
|
38
38
|
const HELP = `用法: car <command> [args]
|
|
39
|
-
run <plugin.ts> [--sig-enforce] [--config <path>]
|
|
39
|
+
run <plugin.ts> [--sig-enforce] [--config <path>] [--adapter-id <id>]
|
|
40
40
|
装配并运行插件(快速上手流;装载前签名门 warn 缺省)
|
|
41
|
+
真路径:--prompt <text> + --base-url/--model(或 llm.* 配置);
|
|
42
|
+
--adapter-id anthropic = Messages API(凭据 ANTHROPIC_API_KEY);
|
|
43
|
+
--demo = stub 演示流
|
|
41
44
|
reload <file|dir> [--sig-enforce] [--config <path>]
|
|
42
45
|
热重载插件并打印六阶段加载报告(含 verify 签名门)
|
|
43
46
|
session verify <file> 哈希链完整性校验(撕裂尾显式报告)
|
|
@@ -49,6 +52,8 @@ const HELP = `用法: car <command> [args]
|
|
|
49
52
|
session verify/replay/export 均支持 --session <id> [--db <path>]
|
|
50
53
|
经 SQLite 索引定位会话(1.2;缺省 db = cwd sessions-index.db;索引未收录 = 显式报错)
|
|
51
54
|
doctor 环境自检
|
|
55
|
+
mcp-serve [--host <id>] [--plugin <file|dir>...] [--config <path>]
|
|
56
|
+
CAR-as-MCP-Server(宿主经 stdio JSON-RPC 接入)
|
|
52
57
|
plugin-sign keygen [--out <前缀>] [--force]
|
|
53
58
|
生成 ed25519 签名密钥对(<前缀>.priv PKCS8 DER / <前缀>.pub SPKI base64;缺省前缀 car-release)
|
|
54
59
|
plugin-sign sign <file...> [--key <priv>]
|
|
@@ -57,8 +62,10 @@ const HELP = `用法: car <command> [args]
|
|
|
57
62
|
验证签名(信任根序:--trust-root > CAR_TRUST_ROOT > ./car-release.pub;缺签/坏签均 FAIL)
|
|
58
63
|
签名门环境变量:CAR_SIG_ENFORCE=1 切 enforce;CAR_TRUST_ROOT=<ed25519 spki base64>;
|
|
59
64
|
CAR_UNSIGNED_ALLOW=1 显式声明豁免(warn 缺签路径计数 confirmed=yes,横幅保留)
|
|
60
|
-
配置文件通道(1.
|
|
61
|
-
sandbox.
|
|
65
|
+
配置文件通道(1.5 起双载体):cwd car.config.ts(export default {…};发现序 ts > json)或
|
|
66
|
+
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`
|
|
62
69
|
|
|
63
70
|
// 进程内热重载会话(ReloadManager 持有 epoch 与实例注册表;同进程连续 reload 语义完整)
|
|
64
71
|
let reloadMgrPromise: Promise<import('./load/report.ts').ReloadManager> | undefined
|
|
@@ -101,6 +108,8 @@ function resolveSigGate(argv: string[]): { options: import('./load/sigGate.ts').
|
|
|
101
108
|
|
|
102
109
|
async function main(): Promise<number> {
|
|
103
110
|
const [, , cmd, ...rest] = process.argv
|
|
111
|
+
// 1.5-S3(D-22):car.config.ts 载体统一预热(--config *.ts 或 cwd 发现序 ts>json;无 TS 载体 = no-op)
|
|
112
|
+
await warmCarConfig({ explicitPath: flagValue(rest, '--config') })
|
|
104
113
|
switch (cmd) {
|
|
105
114
|
case 'run': {
|
|
106
115
|
// 1.4-S5(D-13):缺省 = 真实模型路径(chatStep 经 openai-compat 适配器);--demo 保留 stub 演示流。
|
|
@@ -186,8 +195,9 @@ async function main(): Promise<number> {
|
|
|
186
195
|
const prompt = flagValue(rest, '--prompt')
|
|
187
196
|
if (!llm.baseUrl || !llm.model || prompt === undefined) {
|
|
188
197
|
console.error('CAR-E-LLM-CONFIG: 真实模型路径需要 baseUrl + model + --prompt——')
|
|
189
|
-
console.error(' 配置:car.config.json {"llm":{"baseUrl":"https://…","model":"…"}} / env CAR_LLM_BASE_URL+CAR_LLM_MODEL / flag --base-url --model --prompt')
|
|
190
|
-
console.error('
|
|
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')
|
|
191
201
|
console.error(' (无模型演示流:--demo)')
|
|
192
202
|
return 2
|
|
193
203
|
}
|
|
@@ -195,12 +205,15 @@ async function main(): Promise<number> {
|
|
|
195
205
|
const credService = new CredentialService()
|
|
196
206
|
const core = new RuntimeCore(credService)
|
|
197
207
|
core.bindContext(ctx)
|
|
198
|
-
// 适配器经根作用域 effect 可逆注册(bindContext 必须先于 register
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
credentials: credService,
|
|
202
|
-
|
|
203
|
-
|
|
208
|
+
// 适配器经根作用域 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 })
|
|
204
217
|
// MCP 工具进 turn(D-18):--mcp <serverId>(可重复;server 启动规格在 car.config.json mcp.servers 登记)
|
|
205
218
|
const mcpIds: string[] = []
|
|
206
219
|
for (let i = 0; i < rest.length; i++) {
|
|
@@ -214,11 +227,15 @@ async function main(): Promise<number> {
|
|
|
214
227
|
if (mcpIds.length) {
|
|
215
228
|
const { McpGateway } = await import('./mcp/gateway.ts')
|
|
216
229
|
const { createStdioClientTransport } = await import('./mcp/stdioClient.ts')
|
|
230
|
+
const { createHttpMcpTransport, resolveMcpHeaders } = await import('./mcp/httpClient.ts')
|
|
217
231
|
const gw = new McpGateway()
|
|
218
232
|
for (const id of mcpIds) {
|
|
219
233
|
const spec = cfgLlm.config.mcp?.servers?.[id]
|
|
220
|
-
if (!spec) { console.error(`CAR-E-MCP: server "${id}"
|
|
221
|
-
|
|
234
|
+
if (!spec) { console.error(`CAR-E-MCP: server "${id}" 未在配置 mcp.servers 登记`); return 2 }
|
|
235
|
+
// 1.5-S2(D-21):url = 远程 Streamable HTTP(懒 initialize + 会话头);command = stdio 本地进程
|
|
236
|
+
const transport = spec.url
|
|
237
|
+
? createHttpMcpTransport({ url: spec.url, headers: resolveMcpHeaders(spec.headers ?? {}) })
|
|
238
|
+
: createStdioClientTransport({ command: spec.command!, args: spec.args })
|
|
222
239
|
mcpTransports.push(transport)
|
|
223
240
|
const mcpTools = await gw.register({ serverId: id, transport, env: spec.env })
|
|
224
241
|
for (const t of mcpTools) {
|
|
@@ -444,9 +461,9 @@ async function main(): Promise<number> {
|
|
|
444
461
|
// E-6 宿主实连入口(M4-S18):CAR-as-MCP-Server——宿主(Claude Code/Codex)经 stdio JSON-RPC 接入
|
|
445
462
|
// 1.1-S4 装载接线:--plugin <file|dir>(可重复)/ CAR_PLUGINS env → 六阶段流水线(verify 门先于
|
|
446
463
|
// import())挂载进 Context + car_load_total/car_unsigned_confirmed 全链计数。
|
|
447
|
-
//
|
|
448
|
-
// (
|
|
449
|
-
// (
|
|
464
|
+
// 1.6-S2(D-25)宿主工具面透传接线:插件工具 Map 注入 RuntimeFacade——tool_list 出声明面
|
|
465
|
+
// (declaredSideEffect 不出站:权限面只进权限门红线)、tool_call 经权限门(write 类 policy
|
|
466
|
+
// 拒绝/readonly 执行)+ 成对落链;跨源同名 CAR-E-DUP fail-closed(1.4-S1 纪律同款)。
|
|
450
467
|
const hostIdx = rest.indexOf('--host')
|
|
451
468
|
const { HOST_MAPPINGS } = await import('./host/mappings.ts')
|
|
452
469
|
// 默认宿主取映射表首项(数据驱动,静态架构断言红线:宿主标识只在 mappings 数据文件)
|
|
@@ -470,11 +487,12 @@ async function main(): Promise<number> {
|
|
|
470
487
|
counters.onCount(name, labels)
|
|
471
488
|
if (otel.enabled) otel.getMeter().createCounter(name).add(1, labels as Record<string, string> | undefined)
|
|
472
489
|
}
|
|
490
|
+
// 1.6-S2:hostTools 提升到块外——跨源同名查重与 toolMap 装配在装载块之后统一收口
|
|
491
|
+
const hostTools: Array<{ name: string; description?: string; parameters?: unknown; declaredSideEffect?: 'readonly' | 'write'; run: (args: Record<string, unknown>) => Promise<unknown> }> = []
|
|
473
492
|
if (pluginsArg.sources.length) {
|
|
474
493
|
const { loadPlugins, formatLoadReport } = await import('./load/report.ts')
|
|
475
494
|
const { Context } = await import('./kernel/context.ts')
|
|
476
495
|
const ctx = new Context()
|
|
477
|
-
const hostTools: any[] = []
|
|
478
496
|
for (const src of pluginsArg.sources) {
|
|
479
497
|
const result = await loadPlugins({ source: src, signature: { ...sig.options, onCount: (n, l) => onCount(n, l) } })
|
|
480
498
|
if (result.report.stages.some(s => s.status === 'FAIL')) {
|
|
@@ -494,14 +512,18 @@ async function main(): Promise<number> {
|
|
|
494
512
|
} })
|
|
495
513
|
}
|
|
496
514
|
const names = result.plugins.map(p => `${p.manifest.name}@${p.manifest.version}`).join(', ')
|
|
497
|
-
console.error(`[car mcp-serve] plugins loaded: ${names}(tools: ${hostTools.map(t => t.name).join(', ') || '无'}
|
|
515
|
+
console.error(`[car mcp-serve] plugins loaded: ${names}(tools: ${hostTools.map(t => t.name).join(', ') || '无'};装载+工具面透传已接线——1.6-S2)`)
|
|
498
516
|
}
|
|
499
517
|
}
|
|
500
518
|
const { HostGateway } = await import('./host/hostGateway.ts')
|
|
501
519
|
const { createRuntimeFacade } = await import('./host/facade.ts')
|
|
502
520
|
const { createStdioServer } = await import('./host/stdio.ts')
|
|
503
521
|
const profiles = new Map(HOST_MAPPINGS.map(h => [h.hostId, h]))
|
|
504
|
-
|
|
522
|
+
// 1.6-S2:跨源同名显式报错(禁静默覆盖——last-wins Map 语义下同名即歧义,fail-closed 启动中止)
|
|
523
|
+
const dupTool = hostTools.find((t, i) => hostTools.findIndex(x => x.name === t.name) !== i)
|
|
524
|
+
if (dupTool) { console.error(`CAR-E-DUP: 工具 "${dupTool.name}" 跨源同名——禁静默覆盖(mcp-serve fail-closed 启动中止)`); return 1 }
|
|
525
|
+
const toolMap = new Map(hostTools.map(t => [t.name as string, t]))
|
|
526
|
+
const facade = createRuntimeFacade({ profiles, ...(toolMap.size ? { tools: toolMap } : {}) })
|
|
505
527
|
const gw = new HostGateway({ facade, audit: e => onCount('car_registry_decision', { source: 'registry' }) })
|
|
506
528
|
for (const h of HOST_MAPPINGS) gw.registerHost({ hostId: h.hostId, profile: h, transport: {} as never })
|
|
507
529
|
const server = createStdioServer((tool, args) => gw.handle(hostId, tool, args))
|
package/src/dx/doctor.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* 门禁判定仍在 sigGate/verifier;公钥可打印、信任根值本身不打印
|
|
9
9
|
*/
|
|
10
10
|
import { createPublicKey } from 'node:crypto'
|
|
11
|
-
import { CredentialService } from '../runtime-core/credentials.ts'
|
|
11
|
+
import { CredentialService, providerEnvVars } from '../runtime-core/credentials.ts'
|
|
12
12
|
import { loadCarConfig, mergeSignatureGate, mergeLlmConfig } from '../load/config.ts'
|
|
13
13
|
|
|
14
14
|
export interface CredentialCheck {
|
|
@@ -106,24 +106,26 @@ export interface ModelReadinessCheck {
|
|
|
106
106
|
export function doctorModelReadiness(opts: { env?: NodeJS.ProcessEnv; cwd?: string; configPath?: string } = {}): ModelReadinessCheck {
|
|
107
107
|
const env = opts.env ?? process.env
|
|
108
108
|
const cfg = loadCarConfig({ explicitPath: opts.configPath, cwd: opts.cwd })
|
|
109
|
-
const { baseUrl, model, allowEnvFallback } = mergeLlmConfig(env, cfg.error ? {} : cfg.config)
|
|
109
|
+
const { baseUrl, model, adapterId, allowEnvFallback } = mergeLlmConfig(env, cfg.error ? {} : cfg.config)
|
|
110
|
+
// 1.5-S4(D-20 配套):adapterId → 凭据 provider 感知(anthropic → ANTHROPIC_API_KEY;缺省 openai-compat)
|
|
111
|
+
const provider = adapterId === 'anthropic' ? 'anthropic' : 'openai-compat'
|
|
110
112
|
const envFallback: ModelReadinessCheck['envFallback'] = allowEnvFallback ? 'on' : 'off'
|
|
111
113
|
let credential: ModelReadinessCheck['credential'] = 'not-found'
|
|
112
114
|
if (allowEnvFallback) {
|
|
113
|
-
// env fallback 显式开启:
|
|
114
|
-
if (
|
|
115
|
+
// env fallback 显式开启:provider 候选变量存在性(providerEnvVars;只探存在不取值)
|
|
116
|
+
if (providerEnvVars(provider).some(n => env[n])) credential = 'env'
|
|
115
117
|
}
|
|
116
118
|
if (credential === 'not-found') {
|
|
117
119
|
// keychain 通道存在性(resolve 探测,A080001 全落空即 not-found;值不打印)
|
|
118
120
|
try {
|
|
119
|
-
const ref = new CredentialService().resolve(
|
|
121
|
+
const ref = new CredentialService().resolve(provider, { env, allowEnvFallback })
|
|
120
122
|
credential = ref.source
|
|
121
123
|
} catch { /* 全落空 = not-found(登记口径,非错误) */ }
|
|
122
124
|
}
|
|
123
125
|
const configured = !!baseUrl && !!model
|
|
124
126
|
const parts = [
|
|
125
|
-
configured ? `baseUrl=${baseUrl} model=${model}` : 'baseUrl/model 未配置(car run 真路径需 llm.* 配置或 CAR_LLM_BASE_URL/CAR_LLM_MODEL——见 car doctor / 配置指南)',
|
|
126
|
-
credential === 'keychain' ?
|
|
127
|
+
configured ? `baseUrl=${baseUrl} model=${model}${adapterId ? ` adapter=${adapterId}` : ''}` : 'baseUrl/model 未配置(car run 真路径需 llm.* 配置或 CAR_LLM_BASE_URL/CAR_LLM_MODEL——见 car doctor / 配置指南)',
|
|
128
|
+
credential === 'keychain' ? `凭据=keychain 命中(car-runtime/${provider},值不打印)`
|
|
127
129
|
: credential === 'env' ? '凭据=env 命中(显式开启 fallback,值不打印)'
|
|
128
130
|
: '凭据未就绪(keychain 未命中且 env fallback 未开或变量缺席——A080001 会引导至此)',
|
|
129
131
|
`envFallback=${envFallback}`,
|
package/src/host/facade.ts
CHANGED
|
@@ -7,14 +7,24 @@
|
|
|
7
7
|
* 未命中事件走 hostRaw 降级(零静默);turnEnd reason 由最后一条归一化 turnEnd 决定(缺省 completed);
|
|
8
8
|
* - 模型循环由宿主驱动(「统一边界而非统一内部」):CAR 承载日志/审计/工具面,step 循环不暴露;
|
|
9
9
|
* - 会话内存态 Map 承载(v0.2.0 SessionLog 语义),持久化经 sessionExport(取证包)与后续 S13 文件落盘增强。
|
|
10
|
+
* - 1.6-S2(D-25)宿主工具面透传:opts.tools 注入插件工具 Map(mcp-serve 装配)——
|
|
11
|
+
* toolList 出 name/description/parameters(declaredSideEffect 不出站:权限面只进权限门红线);
|
|
12
|
+
* toolCall 经权限门(T-22 缺省 write 收口;write 类 policy 拒绝——mcp-serve 无人审回路,
|
|
13
|
+
* 人审 confirm 属 turn 循环 preset.authorize 通道不外推到宿主面)+ 成对落链
|
|
14
|
+
* (actor='user' 宿主=用户代理 / turnId='T0' 会话级能力面,sessionTurn 从 T1 起不冲突);
|
|
15
|
+
* 未装配 tools 时显式报错(S13 占位「静默空面」退役——fail-visible)。
|
|
10
16
|
*/
|
|
11
17
|
import { SessionLog, type EventKind } from '../session/log.ts'
|
|
12
18
|
import { forkCrossHost, loadForkedLog } from '../session/fork.ts'
|
|
13
19
|
import { deriveSessionId, normalizeHostEvent } from './mappings.ts'
|
|
14
20
|
import type { HostProfile } from './mappings.ts'
|
|
15
21
|
import type { RuntimeFacade } from './hostGateway.ts'
|
|
22
|
+
import type { ToolReg } from '../load/loader.ts'
|
|
16
23
|
|
|
17
|
-
interface SessionState { log: SessionLog; profile: HostProfile; hostSessionId: string; turnCount: number; lastReason: string }
|
|
24
|
+
interface SessionState { log: SessionLog; profile: HostProfile; hostSessionId: string; turnCount: number; lastReason: string; hostToolSeq: number }
|
|
25
|
+
|
|
26
|
+
/** 宿主工具面声明形态(toolList 出站——权限面字段不出站) */
|
|
27
|
+
export interface HostToolDescriptor { name: string; description?: string; parameters?: unknown }
|
|
18
28
|
|
|
19
29
|
/** 迁移事件里已用过的最大 turn 序号(T{n})——import 侧续跑 turnId 不与迁移段冲突 */
|
|
20
30
|
function maxTurnIndex(log: SessionLog): number {
|
|
@@ -26,7 +36,7 @@ function maxTurnIndex(log: SessionLog): number {
|
|
|
26
36
|
return max
|
|
27
37
|
}
|
|
28
38
|
|
|
29
|
-
export function createRuntimeFacade(opts: { profiles: Map<string, HostProfile> }): RuntimeFacade {
|
|
39
|
+
export function createRuntimeFacade(opts: { profiles: Map<string, HostProfile>; tools?: Map<string, ToolReg> }): RuntimeFacade {
|
|
30
40
|
const sessions = new Map<string, SessionState>()
|
|
31
41
|
|
|
32
42
|
const bySession = (sessionId: string): SessionState => {
|
|
@@ -47,12 +57,12 @@ export function createRuntimeFacade(opts: { profiles: Map<string, HostProfile> }
|
|
|
47
57
|
if (importJsonl !== undefined) {
|
|
48
58
|
const { log } = loadForkedLog(importJsonl, { hostId, hostSessionId })
|
|
49
59
|
const turnCount = maxTurnIndex(log)
|
|
50
|
-
sessions.set(sessionId, { log, profile, hostSessionId, turnCount, lastReason: 'completed' })
|
|
60
|
+
sessions.set(sessionId, { log, profile, hostSessionId, turnCount, lastReason: 'completed', hostToolSeq: 0 })
|
|
51
61
|
return { sessionId }
|
|
52
62
|
}
|
|
53
63
|
const log = new SessionLog(sessionId)
|
|
54
64
|
log.append('runtime', 'hostRaw', 'T0', { hostEvent: 'session_registered', hostId, hostSessionId })
|
|
55
|
-
sessions.set(sessionId, { log, profile, hostSessionId, turnCount: 0, lastReason: 'completed' })
|
|
65
|
+
sessions.set(sessionId, { log, profile, hostSessionId, turnCount: 0, lastReason: 'completed', hostToolSeq: 0 })
|
|
56
66
|
return { sessionId }
|
|
57
67
|
},
|
|
58
68
|
|
|
@@ -115,11 +125,38 @@ export function createRuntimeFacade(opts: { profiles: Map<string, HostProfile> }
|
|
|
115
125
|
},
|
|
116
126
|
|
|
117
127
|
async toolList() {
|
|
118
|
-
return { tools: [] } //
|
|
128
|
+
if (!opts.tools) return { tools: [] } // 未装配 = 空面(与占位形态兼容);装配后出声明面
|
|
129
|
+
const out: HostToolDescriptor[] = []
|
|
130
|
+
for (const t of opts.tools.values()) {
|
|
131
|
+
out.push({ name: t.name, ...(t.description !== undefined ? { description: t.description } : {}), ...(t.parameters !== undefined ? { parameters: t.parameters } : {}) })
|
|
132
|
+
}
|
|
133
|
+
return { tools: out }
|
|
119
134
|
},
|
|
120
135
|
|
|
121
|
-
|
|
122
|
-
|
|
136
|
+
// 1.6-S2(D-25):权限门语义——未知工具显式报错;T-22 缺省 write;write 类 policy 拒绝
|
|
137
|
+
//(mcp-serve 无人审回路,deny-by-default);readonly 执行;工具异常=成对错误结果(US-5 同款)。
|
|
138
|
+
// 全路径成对落链(toolCall/toolResult)——宿主面调用 100% 留痕哈希链。
|
|
139
|
+
async toolCall(args) {
|
|
140
|
+
const { sessionId, tool, arguments: toolArgs } = args as { sessionId?: string; tool?: string; arguments?: Record<string, unknown> }
|
|
141
|
+
const s = bySession(String(sessionId))
|
|
142
|
+
if (!opts.tools) throw new Error('CAR-E-HOST: tool 面未装配(mcp-serve 未登记插件工具——tool_list/tool_call 不可用)')
|
|
143
|
+
const id = `host-tc-${++s.hostToolSeq}`
|
|
144
|
+
s.log.append('user', 'toolCall', 'T0', { id, tool: String(tool), args: toolArgs ?? {} })
|
|
145
|
+
const def = opts.tools.get(String(tool))
|
|
146
|
+
if (!def) {
|
|
147
|
+
s.log.append('plugin', 'toolResult', 'T0', { id, error: `unknown tool "${tool}"` })
|
|
148
|
+
return { ok: false, error: `unknown tool "${tool}"(tool_list 查看已装配工具面)` }
|
|
149
|
+
}
|
|
150
|
+
const sideEffect = def.declaredSideEffect ?? 'write' // T-22 收口:未声明按最高约束
|
|
151
|
+
if (sideEffect === 'write') {
|
|
152
|
+
s.log.append('runtime', 'toolResult', 'T0', { id, error: 'authorization-denied', granted: false, mode: 'host-policy' })
|
|
153
|
+
return { ok: false, error: 'CAR-E-HOST-AUTHZ: write 类工具经宿主面 policy 拒绝(mcp-serve 无人审回路——D-25 deny-by-default)' }
|
|
154
|
+
}
|
|
155
|
+
let result: unknown
|
|
156
|
+
let toolError: string | undefined
|
|
157
|
+
try { result = await def.run(toolArgs ?? {}) } catch (e) { toolError = String(e) }
|
|
158
|
+
s.log.append('plugin', 'toolResult', 'T0', { id, result, ...(toolError ? { error: toolError } : {}) })
|
|
159
|
+
return toolError !== undefined ? { ok: false, error: toolError } : { ok: true, result }
|
|
123
160
|
},
|
|
124
161
|
}
|
|
125
162
|
}
|
package/src/host/hostGateway.ts
CHANGED
|
@@ -29,7 +29,8 @@ export interface RuntimeFacade {
|
|
|
29
29
|
sessionExport(args: { sessionId: string }): Promise<{ bundle: unknown }>
|
|
30
30
|
/** M5-DEC4 转正:跨宿主 fork(宿主 A 侧调用,产出可迁移工件 + 宿主 B 侧确定性会话 id) */
|
|
31
31
|
sessionFork(args: { sessionId: string; targetHostId: string; targetHostSessionId: string; upToSeq?: number }): Promise<{ sessionId: string; jsonl: string; migrated: number }>
|
|
32
|
-
|
|
32
|
+
/** 1.6-S2(D-25):声明面出站(name/description/parameters——declaredSideEffect 权限面不出站);未装配 = 空面 */
|
|
33
|
+
toolList(args: Record<string, never>): Promise<{ tools: Array<{ name: string; description?: string; parameters?: unknown }> }>
|
|
33
34
|
toolCall(args: { sessionId: string; tool: string; arguments?: Record<string, unknown> }): Promise<{ ok: boolean; result?: unknown; error?: string }>
|
|
34
35
|
}
|
|
35
36
|
|
package/src/host/stdio.ts
CHANGED
|
@@ -21,6 +21,9 @@ export interface StdioServer {
|
|
|
21
21
|
|
|
22
22
|
export function createStdioServer(dispatch: StdioDispatcher): StdioServer {
|
|
23
23
|
let nextId = 0
|
|
24
|
+
// 1.6-S2:tools/call 按请求序串行派发(会话态依赖 + 响应序确定性——JSON-RPC 允许乱序,
|
|
25
|
+
// 但 stdio 宿主与测试按序解析;tools/list 等同步方法不受此链影响)
|
|
26
|
+
let callChain: Promise<void> = Promise.resolve()
|
|
24
27
|
return {
|
|
25
28
|
async serve(input, output) {
|
|
26
29
|
let count = 0
|
|
@@ -59,15 +62,19 @@ export function createStdioServer(dispatch: StdioDispatcher): StdioServer {
|
|
|
59
62
|
if (req.method === 'tools/call') {
|
|
60
63
|
const name = req.params?.name ?? ''
|
|
61
64
|
const args = req.params?.arguments ?? {}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
callChain = callChain
|
|
66
|
+
.then(() => dispatch(name, args))
|
|
67
|
+
.then(r => {
|
|
68
|
+
if (r.ok) respond({ content: [{ type: 'text', text: JSON.stringify(r.result ?? {}) }], carOk: true })
|
|
69
|
+
else respond(undefined, { code: -32000, message: r.error ?? 'host call failed' })
|
|
70
|
+
})
|
|
71
|
+
.catch(e => respond(undefined, { code: -32000, message: String(e) }))
|
|
66
72
|
return
|
|
67
73
|
}
|
|
68
74
|
respond(undefined, { code: -32601, message: `unknown method "${req.method}"(支持 initialize / tools/list / tools/call)` })
|
|
69
75
|
})
|
|
70
76
|
await done
|
|
77
|
+
await callChain // 串行链收口:进程退出前最后一批调用响应写完(win32 退出断言同源教训)
|
|
71
78
|
return count
|
|
72
79
|
},
|
|
73
80
|
}
|