@lqc123qwe/car-runtime 1.9.0 → 1.10.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.
Files changed (176) hide show
  1. package/README.md +17 -1
  2. package/dist/SHA256SUMS +1 -0
  3. package/dist/authz/authz.d.ts +44 -0
  4. package/dist/authz/authz.js +65 -0
  5. package/dist/car-runtime-1.10.0.tgz +0 -0
  6. package/dist/car-runtime-1.10.0.tgz.sig.bundle +1 -0
  7. package/dist/cli.d.ts +1 -0
  8. package/dist/cli.js +825 -0
  9. package/dist/dx/doctor.d.ts +62 -0
  10. package/dist/dx/doctor.js +127 -0
  11. package/dist/governance/dump.d.ts +47 -0
  12. package/dist/governance/dump.js +17 -0
  13. package/dist/host/facade.d.ts +13 -0
  14. package/dist/host/facade.js +154 -0
  15. package/dist/host/hostGateway.d.ts +117 -0
  16. package/dist/host/hostGateway.js +65 -0
  17. package/dist/host/mappings.d.ts +27 -0
  18. package/dist/host/mappings.js +52 -0
  19. package/dist/host/stdio.d.ts +14 -0
  20. package/dist/host/stdio.js +83 -0
  21. package/dist/kernel/context.d.ts +76 -0
  22. package/dist/kernel/context.js +203 -0
  23. package/dist/kernel/events.d.ts +37 -0
  24. package/dist/kernel/events.js +82 -0
  25. package/dist/load/config.d.ts +79 -0
  26. package/dist/load/config.js +285 -0
  27. package/dist/load/loader.d.ts +72 -0
  28. package/dist/load/loader.js +162 -0
  29. package/dist/load/registry.d.ts +44 -0
  30. package/dist/load/registry.js +58 -0
  31. package/dist/load/report.d.ts +98 -0
  32. package/dist/load/report.js +301 -0
  33. package/dist/load/sigGate.d.ts +22 -0
  34. package/dist/load/sigGate.js +68 -0
  35. package/dist/load/sign.d.ts +25 -0
  36. package/dist/load/sign.js +47 -0
  37. package/dist/load/verifier.d.ts +35 -0
  38. package/dist/load/verifier.js +56 -0
  39. package/dist/loop/goal.d.ts +31 -0
  40. package/dist/loop/goal.js +34 -0
  41. package/dist/loop/recover.d.ts +17 -0
  42. package/dist/loop/recover.js +39 -0
  43. package/dist/loop/stop.d.ts +79 -0
  44. package/dist/loop/stop.js +151 -0
  45. package/dist/mcp/gateway.d.ts +96 -0
  46. package/dist/mcp/gateway.js +69 -0
  47. package/dist/mcp/httpClient.d.ts +27 -0
  48. package/dist/mcp/httpClient.js +196 -0
  49. package/dist/mcp/stdioClient.d.ts +8 -0
  50. package/dist/mcp/stdioClient.js +73 -0
  51. package/dist/ptc/budget.d.ts +21 -0
  52. package/dist/ptc/budget.js +36 -0
  53. package/dist/ptc/erasable.d.ts +14 -0
  54. package/dist/ptc/erasable.js +27 -0
  55. package/dist/ptc/runCode.d.ts +65 -0
  56. package/dist/ptc/runCode.js +140 -0
  57. package/dist/ptc/schemaRender.d.ts +15 -0
  58. package/dist/ptc/schemaRender.js +90 -0
  59. package/dist/ptc/sdk.d.ts +27 -0
  60. package/dist/ptc/sdk.js +23 -0
  61. package/dist/ptc/worker-entry.d.ts +1 -0
  62. package/dist/ptc/worker-entry.js +71 -0
  63. package/dist/release-audit.jsonl +4 -0
  64. package/dist/runtime-core/chatStep.d.ts +40 -0
  65. package/dist/runtime-core/chatStep.js +104 -0
  66. package/dist/runtime-core/credentials.d.ts +49 -0
  67. package/dist/runtime-core/credentials.js +118 -0
  68. package/dist/runtime-core/errors.d.ts +30 -0
  69. package/dist/runtime-core/errors.js +36 -0
  70. package/dist/runtime-core/llm.d.ts +76 -0
  71. package/dist/runtime-core/llm.js +553 -0
  72. package/dist/runtime-core/redaction.d.ts +39 -0
  73. package/dist/runtime-core/redaction.js +79 -0
  74. package/dist/runtime-core/telemetry.d.ts +88 -0
  75. package/dist/runtime-core/telemetry.js +332 -0
  76. package/dist/runtime-core/tools.d.ts +15 -0
  77. package/dist/runtime-core/tools.js +12 -0
  78. package/dist/runtime-core/trace.d.ts +55 -0
  79. package/dist/runtime-core/trace.js +137 -0
  80. package/dist/runtime-core/types.d.ts +84 -0
  81. package/dist/runtime-core/types.js +13 -0
  82. package/dist/sandbox/sandbox.d.ts +68 -0
  83. package/dist/sandbox/sandbox.js +153 -0
  84. package/dist/sbom.json +263 -0
  85. package/dist/seam.d.ts +21 -0
  86. package/dist/seam.js +20 -0
  87. package/dist/security/secrets-baseline.d.ts +43 -0
  88. package/dist/security/secrets-baseline.js +176 -0
  89. package/dist/security/secrets.d.ts +45 -0
  90. package/dist/security/secrets.js +138 -0
  91. package/dist/session/export.d.ts +55 -0
  92. package/dist/session/export.js +81 -0
  93. package/dist/session/fork.d.ts +51 -0
  94. package/dist/session/fork.js +63 -0
  95. package/dist/session/format.d.ts +21 -0
  96. package/dist/session/format.js +49 -0
  97. package/dist/session/indexStore.d.ts +44 -0
  98. package/dist/session/indexStore.js +265 -0
  99. package/dist/session/log.d.ts +79 -0
  100. package/dist/session/log.js +157 -0
  101. package/dist/session/store.d.ts +9 -0
  102. package/dist/session/store.js +37 -0
  103. package/dist/telemetry/metrics.d.ts +25 -0
  104. package/dist/telemetry/metrics.js +26 -0
  105. package/package.json +17 -1
  106. package/src/seam.ts +21 -0
  107. package/.gitattributes +0 -11
  108. package/.github/workflows/ci.yml +0 -293
  109. package/.github/workflows/release.yml +0 -54
  110. package/adr/ADR-001.md +0 -4
  111. package/adr/ADR-002.md +0 -5
  112. package/adr/ADR-003.md +0 -35
  113. package/adr/host-onboarding.md +0 -68
  114. package/adr/mlps-audit-checklist.md +0 -41
  115. package/adr/win32-spike.md +0 -45
  116. package/docs/plugin-authoring.md +0 -82
  117. package/native/landlock-run/Makefile +0 -19
  118. package/native/landlock-run/landlock-run.c +0 -162
  119. package/scripts/contract-check.ts +0 -56
  120. package/scripts/invariant-n1.ts +0 -96
  121. package/scripts/package-check.ts +0 -29
  122. package/scripts/ptc-baseline/run-baseline.ts +0 -118
  123. package/scripts/release-artifacts.ts +0 -111
  124. package/scripts/release-pipeline.ts +0 -245
  125. package/scripts/secrets-baseline.ts +0 -33
  126. package/scripts/secrets-scan.ts +0 -56
  127. package/scripts/win32-ffi-debug.ts +0 -143
  128. package/scripts/win32-koffi-spike.ts +0 -537
  129. package/test/contract/eventmap.golden.json +0 -7
  130. package/test/fixtures/hello-plugin.ts +0 -5
  131. package/test/kernel.spec.ts +0 -136
  132. package/test/registry-e2e/run-e2e.ts +0 -213
  133. package/test/s11.spec.ts +0 -129
  134. package/test/s12.spec.ts +0 -135
  135. package/test/s13.spec.ts +0 -117
  136. package/test/s14.spec.ts +0 -117
  137. package/test/s15.spec.ts +0 -80
  138. package/test/s16.spec.ts +0 -133
  139. package/test/s17.spec.ts +0 -102
  140. package/test/s18.spec.ts +0 -81
  141. package/test/s19.spec.ts +0 -138
  142. package/test/s2.spec.ts +0 -245
  143. package/test/s20.spec.ts +0 -241
  144. package/test/s21.spec.ts +0 -231
  145. package/test/s22-m7-store-format.spec.ts +0 -135
  146. package/test/s23-m7-export-index.spec.ts +0 -189
  147. package/test/s24-signature-gate.spec.ts +0 -185
  148. package/test/s25-m8-runtime-core.spec.ts +0 -600
  149. package/test/s26-signature-closeout.spec.ts +0 -349
  150. package/test/s27-host-load-wiring.spec.ts +0 -134
  151. package/test/s28-session-index-followup.spec.ts +0 -236
  152. package/test/s29-otel-producer.spec.ts +0 -252
  153. package/test/s3.spec.ts +0 -179
  154. package/test/s30-release-artifacts.spec.ts +0 -127
  155. package/test/s31-real-model-wiring.spec.ts +0 -329
  156. package/test/s32-telemetry-mcp-ptc.spec.ts +0 -218
  157. package/test/s33-anthropic-adapter.spec.ts +0 -354
  158. package/test/s34-mcp-remote-transport.spec.ts +0 -250
  159. package/test/s35-config-ts-zod.spec.ts +0 -145
  160. package/test/s36-host-tool-surface.spec.ts +0 -250
  161. package/test/s37-projection-dedupe.spec.ts +0 -161
  162. package/test/s38-config-jiti.spec.ts +0 -116
  163. package/test/s39-otlp-span-semantics.spec.ts +0 -100
  164. package/test/s4.spec.ts +0 -169
  165. package/test/s40-metrics-cumulative.spec.ts +0 -67
  166. package/test/s41-mcp-serve-turn-span.spec.ts +0 -148
  167. package/test/s42-traceparent-propagation.spec.ts +0 -415
  168. package/test/s43-remote-http-traceparent.spec.ts +0 -262
  169. package/test/s5.spec.ts +0 -95
  170. package/test/s6.spec.ts +0 -169
  171. package/test/s7.spec.ts +0 -136
  172. package/test/s7b-baseline.spec.ts +0 -20
  173. package/test/s9.spec.ts +0 -89
  174. package/test/sandbox-escape/j17-orchestrate.sh +0 -30
  175. package/test/sandbox-escape/run-matrix.sh +0 -98
  176. package/tsconfig.json +0 -18
@@ -0,0 +1,62 @@
1
+ export interface CredentialCheck {
2
+ name: string;
3
+ envVar: string;
4
+ present: boolean;
5
+ hint: string;
6
+ }
7
+ /** 凭据存在性检查:只报 present/not-set,永不输出值 */
8
+ export declare function doctorCredentials(env?: NodeJS.ProcessEnv): CredentialCheck[];
9
+ export interface ConnectivityResult {
10
+ status: 'PASS' | 'SKIPPED';
11
+ detail: string;
12
+ latencyMs: number;
13
+ }
14
+ /** M8 增强:keychain 通道状态(模型凭据读取面,§3.2.M8.2 凭据行)——能力探测口径,缺席显式降级不静默;离线可过 */
15
+ export declare function doctorKeychain(opts?: {
16
+ platform?: NodeJS.Platform;
17
+ }): {
18
+ available: boolean;
19
+ note: string;
20
+ };
21
+ export interface SignatureCheck {
22
+ /** 生效模式(flag > env > 配置 > 缺省 warn——DEC-1 ②;enforce 缺省翻转 2026-12-25 登记计划内变更) */
23
+ mode: 'warn' | 'enforce';
24
+ /** 信任根通道:absent=未配置(全部插件按 unsigned 处理);valid=在场且可解析;invalid=在场但解析失败(enforce 下拒签风险) */
25
+ trustRoot: 'absent' | 'valid' | 'invalid';
26
+ /** 配置文件通道(1.1-S3):absent=cwd 未发现;found=已发现且校验通过;invalid=发现但解析/校验失败(fail-visible) */
27
+ configFile: 'absent' | 'found' | 'invalid';
28
+ detail: string;
29
+ }
30
+ /** 1.1-S2 签名就绪检查:mode 生效值 + 信任根在场可解析性 + car.config.json 发现态(离线可过;值不打印) */
31
+ export declare function doctorSignature(opts?: {
32
+ env?: NodeJS.ProcessEnv;
33
+ cwd?: string;
34
+ configPath?: string;
35
+ }): SignatureCheck;
36
+ export interface ModelReadinessCheck {
37
+ /** baseUrl + model 均在场 = true(可发起真实模型请求的最低配置面) */
38
+ configured: boolean;
39
+ baseUrl: string;
40
+ model: string;
41
+ /** 凭据通道探测(存在性,值不打印):keychain 命中 / env 命中(显式开启时)/ 全落空 */
42
+ credential: 'keychain' | 'env' | 'not-found';
43
+ /** env fallback 开关态(CAR_ALLOW_ENV_CREDENTIALS=1 或 llm.allowEnvFallback) */
44
+ envFallback: 'on' | 'off';
45
+ detail: string;
46
+ }
47
+ /** 1.4-S3 模型就绪检查(D-16):baseUrl/model 在场性 + 凭据通道存在性探测(离线可过;值不打印) */
48
+ export declare function doctorModelReadiness(opts?: {
49
+ env?: NodeJS.ProcessEnv;
50
+ cwd?: string;
51
+ configPath?: string;
52
+ }): ModelReadinessCheck;
53
+ /**
54
+ * registry 连通性检查:可达 = PASS(含延迟);不可达/显式跳过 = SKIPPED(不失败)。
55
+ * 默认 registry 取 CAR_REGISTRY_URL,缺省 npmjs。
56
+ */
57
+ export declare function doctorConnectivity(opts?: {
58
+ registryUrl?: string;
59
+ timeoutMs?: number;
60
+ /** 测试注入跳过 */
61
+ skip?: boolean;
62
+ }): Promise<ConnectivityResult>;
@@ -0,0 +1,127 @@
1
+ /**
2
+ * M6 · car doctor 增强(《系统设计》§3.2.M6.2 引导行:环境自检——沙箱/凭据/连通性)
3
+ *
4
+ * 口径:
5
+ * - 凭据检查只探存在性、绝不打印值(secrets 红线:值入日志即泄露面)
6
+ * - 连通性检查网络不可达 = 显式 SKIPPED 而非 FAIL——测试环境必须可离线通过(不失败)
7
+ * - 1.1-S2:签名就绪检查行(mode / 信任根 / 配置文件通道发现态)——诊断面只读,
8
+ * 门禁判定仍在 sigGate/verifier;公钥可打印、信任根值本身不打印
9
+ */
10
+ import { createPublicKey } from 'node:crypto';
11
+ import { CredentialService, providerEnvVars } from '../runtime-core/credentials.js';
12
+ import { loadCarConfig, mergeSignatureGate, mergeLlmConfig } from '../load/config.js';
13
+ /** 凭据存在性检查:只报 present/not-set,永不输出值 */
14
+ export function doctorCredentials(env = process.env) {
15
+ return [
16
+ {
17
+ name: 'CAR 凭据',
18
+ envVar: 'CAR_TOKEN',
19
+ present: !!env.CAR_TOKEN,
20
+ hint: 'registry 发布/拉取鉴权用;未设置不影响本地插件开发',
21
+ },
22
+ {
23
+ name: 'npm token',
24
+ envVar: 'NPM_TOKEN',
25
+ present: !!env.NPM_TOKEN,
26
+ hint: 'npm publish 需要时请设置(export NPM_TOKEN=…),值不入日志',
27
+ },
28
+ ];
29
+ }
30
+ /** M8 增强:keychain 通道状态(模型凭据读取面,§3.2.M8.2 凭据行)——能力探测口径,缺席显式降级不静默;离线可过 */
31
+ export function doctorKeychain(opts = {}) {
32
+ return new CredentialService({ platform: opts.platform }).channelStatus;
33
+ }
34
+ /** 1.1-S2 签名就绪检查:mode 生效值 + 信任根在场可解析性 + car.config.json 发现态(离线可过;值不打印) */
35
+ export function doctorSignature(opts = {}) {
36
+ const env = opts.env ?? process.env;
37
+ const cfg = loadCarConfig({ explicitPath: opts.configPath, cwd: opts.cwd });
38
+ let trustRoot = 'absent';
39
+ const raw = env.CAR_TRUST_ROOT || cfg.config.sandbox?.sigTrustRoot;
40
+ if (raw) {
41
+ try {
42
+ createPublicKey({ key: Buffer.from(raw, 'base64'), format: 'der', type: 'spki' });
43
+ trustRoot = 'valid';
44
+ }
45
+ catch {
46
+ trustRoot = 'invalid';
47
+ }
48
+ }
49
+ // 生效模式含配置层(配置文件解析失败时按 env/缺省口径合并,invalid 态在 configFile 显式呈现)
50
+ const { mode } = mergeSignatureGate(env, cfg.error ? {} : cfg.config);
51
+ const configFile = cfg.error ? 'invalid' : cfg.path ? 'found' : 'absent';
52
+ const parts = [
53
+ `mode=${mode}${mode === 'warn' ? '(DEC-1 ② 缺省;2026-12-25 到期翻转)' : ''}`,
54
+ trustRoot === 'absent'
55
+ ? '信任根未配置(全部插件按 unsigned 处理;car plugin-sign keygen 产出 .pub 即信任根)'
56
+ : trustRoot === 'valid'
57
+ ? '信任根已配置(ed25519 spki 可解析,值不打印)'
58
+ : '信任根已配置但不可解析(CAR-E-SIG 拒签风险——检查 CAR_TRUST_ROOT / sandbox.sig.trustRoot 是否为 spki base64)',
59
+ configFile === 'absent'
60
+ ? '配置文件未发现(car.config.json)'
61
+ : configFile === 'found'
62
+ ? `配置文件 ${cfg.path}(校验通过)`
63
+ : `配置文件无效(${cfg.error})`,
64
+ ];
65
+ return { mode, trustRoot, configFile, detail: parts.join(';') };
66
+ }
67
+ /** 1.4-S3 模型就绪检查(D-16):baseUrl/model 在场性 + 凭据通道存在性探测(离线可过;值不打印) */
68
+ export function doctorModelReadiness(opts = {}) {
69
+ const env = opts.env ?? process.env;
70
+ const cfg = loadCarConfig({ explicitPath: opts.configPath, cwd: opts.cwd });
71
+ const { baseUrl, model, adapterId, allowEnvFallback } = mergeLlmConfig(env, cfg.error ? {} : cfg.config);
72
+ // 1.5-S4(D-20 配套):adapterId → 凭据 provider 感知(anthropic → ANTHROPIC_API_KEY;缺省 openai-compat)
73
+ const provider = adapterId === 'anthropic' ? 'anthropic' : 'openai-compat';
74
+ const envFallback = allowEnvFallback ? 'on' : 'off';
75
+ let credential = 'not-found';
76
+ if (allowEnvFallback) {
77
+ // env fallback 显式开启:provider 候选变量存在性(providerEnvVars;只探存在不取值)
78
+ if (providerEnvVars(provider).some(n => env[n]))
79
+ credential = 'env';
80
+ }
81
+ if (credential === 'not-found') {
82
+ // keychain 通道存在性(resolve 探测,A080001 全落空即 not-found;值不打印)
83
+ try {
84
+ const ref = new CredentialService().resolve(provider, { env, allowEnvFallback });
85
+ credential = ref.source;
86
+ }
87
+ catch { /* 全落空 = not-found(登记口径,非错误) */ }
88
+ }
89
+ const configured = !!baseUrl && !!model;
90
+ const parts = [
91
+ configured ? `baseUrl=${baseUrl} model=${model}${adapterId ? ` adapter=${adapterId}` : ''}` : 'baseUrl/model 未配置(car run 真路径需 llm.* 配置或 CAR_LLM_BASE_URL/CAR_LLM_MODEL——见 car doctor / 配置指南)',
92
+ credential === 'keychain' ? `凭据=keychain 命中(car-runtime/${provider},值不打印)`
93
+ : credential === 'env' ? '凭据=env 命中(显式开启 fallback,值不打印)'
94
+ : '凭据未就绪(keychain 未命中且 env fallback 未开或变量缺席——A080001 会引导至此)',
95
+ `envFallback=${envFallback}`,
96
+ ];
97
+ return { configured, baseUrl: baseUrl ?? '', model: model ?? '', credential, envFallback, detail: parts.join(';') };
98
+ }
99
+ /**
100
+ * registry 连通性检查:可达 = PASS(含延迟);不可达/显式跳过 = SKIPPED(不失败)。
101
+ * 默认 registry 取 CAR_REGISTRY_URL,缺省 npmjs。
102
+ */
103
+ export async function doctorConnectivity(opts = {}) {
104
+ const url = opts.registryUrl ?? process.env.CAR_REGISTRY_URL ?? 'https://registry.npmjs.org/-/ping';
105
+ if (opts.skip || process.env.CAR_OFFLINE === '1') {
106
+ return { status: 'SKIPPED', detail: `显式离线(CAR_OFFLINE=1 或测试注入)——跳过 ${url}`, latencyMs: 0 };
107
+ }
108
+ const t0 = Date.now();
109
+ // 同 1.4-BUG-2:AbortSignal.timeout → 手动 controller(进程退出 libuv win32 断言预防)
110
+ const controller = new AbortController();
111
+ const abortTimer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 2000);
112
+ try {
113
+ await fetch(url, { signal: controller.signal });
114
+ return { status: 'PASS', detail: url, latencyMs: Date.now() - t0 };
115
+ }
116
+ catch (e) {
117
+ clearTimeout(abortTimer);
118
+ return {
119
+ status: 'SKIPPED',
120
+ detail: `registry 不可达(${e.name}: ${e.message})——离线环境显式跳过,不判失败`,
121
+ latencyMs: Date.now() - t0,
122
+ };
123
+ }
124
+ finally {
125
+ clearTimeout(abortTimer);
126
+ }
127
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * F14 · 治理视图(M2-S9):配置树 dump + 事件产消图
3
+ *
4
+ * 口径(M2演进规划 F14 / PRD P1-7):
5
+ * - 配置树:装配快照(插件/依赖/服务/副作用计数)——来自 Context.governanceSnapshot();
6
+ * - 事件产消图:@mode 事件目录 × handlerChain(F11 priority 治理视图)——生产者=dispatch 点、
7
+ * 消费者=on 注册(label+priority,按实际执行序);未声明事件在 F7 契约层已被禁止,此图即最终事实;
8
+ * - 输出为纯数据结构(可 JSON 序列化落盘),供 `car governance dump` CLI(S10)与文档生成。
9
+ */
10
+ import type { Context } from '../kernel/context.ts';
11
+ import type { EventBus } from '../kernel/events.ts';
12
+ export interface AssemblyTree {
13
+ plugins: {
14
+ name: string;
15
+ inject: string[];
16
+ state: string;
17
+ effects: number;
18
+ effectLabels: string[];
19
+ provides: string[];
20
+ }[];
21
+ services: {
22
+ name: string;
23
+ provider: string;
24
+ }[];
25
+ pending: string[];
26
+ }
27
+ export declare function dumpAssembly(ctx: Context): AssemblyTree;
28
+ export interface EventGraphEdge {
29
+ event: string;
30
+ mode: string;
31
+ direction: 'producer' | 'consumer';
32
+ detail: string;
33
+ }
34
+ /** 事件产消图:目录内每个事件 → 模式(生产语义)+ 处理器链(消费视图) */
35
+ export declare function dumpEventGraph(bus: EventBus, declared: Map<string, {
36
+ mode: string;
37
+ }>): {
38
+ events: {
39
+ event: string;
40
+ mode: string;
41
+ consumers: {
42
+ label: string;
43
+ priority: number;
44
+ }[];
45
+ }[];
46
+ edges: EventGraphEdge[];
47
+ };
@@ -0,0 +1,17 @@
1
+ export function dumpAssembly(ctx) {
2
+ return ctx.governanceSnapshot();
3
+ }
4
+ /** 事件产消图:目录内每个事件 → 模式(生产语义)+ 处理器链(消费视图) */
5
+ export function dumpEventGraph(bus, declared) {
6
+ const events = [];
7
+ const edges = [];
8
+ for (const [event, { mode }] of declared) {
9
+ const consumers = bus.handlerChain(event);
10
+ events.push({ event, mode, consumers });
11
+ edges.push({ event, mode, direction: 'producer', detail: `dispatch(${mode}) at declared site(@mode 契约机器校验)` });
12
+ for (const c of consumers) {
13
+ edges.push({ event, mode, direction: 'consumer', detail: `${c.label} (priority=${c.priority})` });
14
+ }
15
+ }
16
+ return { events, edges };
17
+ }
@@ -0,0 +1,13 @@
1
+ import type { HostProfile } from './mappings.ts';
2
+ import type { RuntimeFacade } from './hostGateway.ts';
3
+ import type { ToolReg } from '../load/loader.ts';
4
+ /** 宿主工具面声明形态(toolList 出站——权限面字段不出站) */
5
+ export interface HostToolDescriptor {
6
+ name: string;
7
+ description?: string;
8
+ parameters?: unknown;
9
+ }
10
+ export declare function createRuntimeFacade(opts: {
11
+ profiles: Map<string, HostProfile>;
12
+ tools?: Map<string, ToolReg>;
13
+ }): RuntimeFacade;
@@ -0,0 +1,154 @@
1
+ /**
2
+ * T-1 · RuntimeFacade 真实实现(M3-S12):归一化数据流落到 SessionLog
3
+ *
4
+ * 口径(M3系统设计增补 T-1 / 决议⑤):
5
+ * - sessionStart:deriveSessionId 派生 + 首次映射登记事件落哈希链(hostRaw 载体,hostEvent=session_registered);
6
+ * - sessionTurn:宿主报事件批({hostEvent, payload}[])→ 逐条 normalizeHostEvent → append 落盘;
7
+ * 未命中事件走 hostRaw 降级(零静默);turnEnd reason 由最后一条归一化 turnEnd 决定(缺省 completed);
8
+ * - 模型循环由宿主驱动(「统一边界而非统一内部」):CAR 承载日志/审计/工具面,step 循环不暴露;
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)。
16
+ */
17
+ import { SessionLog } from '../session/log.js';
18
+ import { forkCrossHost, loadForkedLog } from '../session/fork.js';
19
+ import { deriveSessionId, normalizeHostEvent } from './mappings.js';
20
+ /** 迁移事件里已用过的最大 turn 序号(T{n})——import 侧续跑 turnId 不与迁移段冲突 */
21
+ function maxTurnIndex(log) {
22
+ let max = 0;
23
+ for (const e of log.events) {
24
+ const m = /^T(\d+)$/.exec(e.turnId);
25
+ if (m)
26
+ max = Math.max(max, Number(m[1]));
27
+ }
28
+ return max;
29
+ }
30
+ export function createRuntimeFacade(opts) {
31
+ const sessions = new Map();
32
+ const bySession = (sessionId) => {
33
+ const s = sessions.get(sessionId);
34
+ if (!s)
35
+ throw new Error(`CAR-E-HOST: session "${sessionId}" not found(session_start 先行)`);
36
+ return s;
37
+ };
38
+ return {
39
+ async sessionStart(args) {
40
+ const { hostSessionId, importJsonl } = args;
41
+ const hostId = args.__hostId;
42
+ const profile = opts.profiles.get(hostId);
43
+ if (!profile)
44
+ throw new Error('CAR-A050001: host profile not registered');
45
+ const sessionId = deriveSessionId(hostId, hostSessionId);
46
+ if (sessions.has(sessionId))
47
+ return { sessionId }; // 幂等:同一宿主会话重复 start 返回同一 CAR 会话
48
+ // M5-DEC4:fork 工件导入(跨宿主 resume)——归属校验(target 必须命中本宿主)+ 断链拒绝
49
+ if (importJsonl !== undefined) {
50
+ const { log } = loadForkedLog(importJsonl, { hostId, hostSessionId });
51
+ const turnCount = maxTurnIndex(log);
52
+ sessions.set(sessionId, { log, profile, hostSessionId, turnCount, lastReason: 'completed', hostToolSeq: 0 });
53
+ return { sessionId };
54
+ }
55
+ const log = new SessionLog(sessionId);
56
+ log.append('runtime', 'hostRaw', 'T0', { hostEvent: 'session_registered', hostId, hostSessionId });
57
+ sessions.set(sessionId, { log, profile, hostSessionId, turnCount: 0, lastReason: 'completed', hostToolSeq: 0 });
58
+ return { sessionId };
59
+ },
60
+ // M5-DEC4 转正:跨宿主 fork——源会话链逐字节迁移 + fork 标记落链,产出可迁移 JSONL 工件
61
+ async sessionFork({ sessionId, targetHostId, targetHostSessionId, upToSeq }) {
62
+ const s = bySession(sessionId);
63
+ if (!opts.profiles.has(targetHostId))
64
+ throw new Error(`CAR-A050001: target host "${targetHostId}" not registered(目标宿主须登记)`);
65
+ const fork = forkCrossHost(s.log, { hostId: targetHostId, hostSessionId: targetHostSessionId }, { upToSeq });
66
+ return { sessionId: fork.sessionId, jsonl: fork.jsonl, migrated: fork.migrated };
67
+ },
68
+ async sessionTurn({ sessionId, input }) {
69
+ const s = bySession(sessionId);
70
+ s.turnCount++;
71
+ const turnId = `T${s.turnCount}`;
72
+ let reason = 'completed';
73
+ const batch = input.events ?? [];
74
+ for (const ev of batch) {
75
+ const n = normalizeHostEvent(s.profile, ev.hostEvent, ev.payload ?? {});
76
+ s.log.append(n.actor, n.kind, turnId, n.payload);
77
+ if (n.kind === 'turnEnd') {
78
+ const r = n.payload.reason;
79
+ if (r)
80
+ reason = r;
81
+ }
82
+ }
83
+ s.lastReason = reason;
84
+ return { reason, steps: s.turnCount };
85
+ },
86
+ async sessionStop({ sessionId }) {
87
+ const s = bySession(sessionId);
88
+ s.log.append('runtime', 'turnEnd', `T${s.turnCount}`, null, { reason: 'aborted' });
89
+ s.lastReason = 'aborted';
90
+ return { reason: 'aborted' };
91
+ },
92
+ async sessionStatus({ sessionId }) {
93
+ const s = bySession(sessionId);
94
+ return { state: 'idle', lastReason: s.lastReason };
95
+ },
96
+ async sessionReplay({ sessionId }) {
97
+ return { messages: bySession(sessionId).log.deriveMessages() };
98
+ },
99
+ async sessionVerify({ sessionId }) {
100
+ const brokenAt = bySession(sessionId).log.verifyChain();
101
+ return { ok: brokenAt === null, brokenAt };
102
+ },
103
+ async sessionExport({ sessionId }) {
104
+ const s = bySession(sessionId);
105
+ // 延迟 import 避免与 facade 类型循环(exportForensicsBundle 依赖 SessionLog 类型)
106
+ const { exportForensicsBundle } = await import('../session/export.js');
107
+ const bundle = exportForensicsBundle(s.log, {
108
+ sessionId, exportedBy: 'host:' + s.profile.hostId, exportedAt: new Date().toISOString(),
109
+ runtimeVersion: '0.2.0', sessionRange: { fromSeq: 0, toSeq: s.log.events.length - 1 },
110
+ });
111
+ return { bundle };
112
+ },
113
+ async toolList() {
114
+ if (!opts.tools)
115
+ return { tools: [] }; // 未装配 = 空面(与占位形态兼容);装配后出声明面
116
+ const out = [];
117
+ for (const t of opts.tools.values()) {
118
+ out.push({ name: t.name, ...(t.description !== undefined ? { description: t.description } : {}), ...(t.parameters !== undefined ? { parameters: t.parameters } : {}) });
119
+ }
120
+ return { tools: out };
121
+ },
122
+ // 1.6-S2(D-25):权限门语义——未知工具显式报错;T-22 缺省 write;write 类 policy 拒绝
123
+ //(mcp-serve 无人审回路,deny-by-default);readonly 执行;工具异常=成对错误结果(US-5 同款)。
124
+ // 全路径成对落链(toolCall/toolResult)——宿主面调用 100% 留痕哈希链。
125
+ async toolCall(args) {
126
+ const { sessionId, tool, arguments: toolArgs } = args;
127
+ const s = bySession(String(sessionId));
128
+ if (!opts.tools)
129
+ throw new Error('CAR-E-HOST: tool 面未装配(mcp-serve 未登记插件工具——tool_list/tool_call 不可用)');
130
+ const id = `host-tc-${++s.hostToolSeq}`;
131
+ s.log.append('user', 'toolCall', 'T0', { id, tool: String(tool), args: toolArgs ?? {} });
132
+ const def = opts.tools.get(String(tool));
133
+ if (!def) {
134
+ s.log.append('plugin', 'toolResult', 'T0', { id, error: `unknown tool "${tool}"` });
135
+ return { ok: false, error: `unknown tool "${tool}"(tool_list 查看已装配工具面)` };
136
+ }
137
+ const sideEffect = def.declaredSideEffect ?? 'write'; // T-22 收口:未声明按最高约束
138
+ if (sideEffect === 'write') {
139
+ s.log.append('runtime', 'toolResult', 'T0', { id, error: 'authorization-denied', granted: false, mode: 'host-policy' });
140
+ return { ok: false, error: 'CAR-E-HOST-AUTHZ: write 类工具经宿主面 policy 拒绝(mcp-serve 无人审回路——D-25 deny-by-default)' };
141
+ }
142
+ let result;
143
+ let toolError;
144
+ try {
145
+ result = await def.run(toolArgs ?? {});
146
+ }
147
+ catch (e) {
148
+ toolError = String(e);
149
+ }
150
+ s.log.append('plugin', 'toolResult', 'T0', { id, result, ...(toolError ? { error: toolError } : {}) });
151
+ return toolError !== undefined ? { ok: false, error: toolError } : { ok: true, result };
152
+ },
153
+ };
154
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * T-1 · HostGateway(M3-S11 骨架):CAR-as-MCP-Server 接入面
3
+ *
4
+ * 口径(M3系统设计增补 T-1 / 决议⑤ MCP 统一边界双宿主等价):
5
+ * - 10 tool 粗粒度会话级能力 + 工具面透传:session_start/turn/stop/status/replay/verify/export/fork
6
+ * + tool_list/tool_call——step 循环不暴露(「统一边界而非统一内部」);
7
+ * session_fork 为 M5 DEC-4 转正项(跨宿主 fork/resume,M3 §1.7 显式延后 → 1.0 纳入);
8
+ * - hostId 登记制(A050001 语义扩展):未登记宿主拒绝连接;
9
+ * - ServerTransport 对偶(gateway.ts):宿主作为 MCP client 经 tools/call 调用;
10
+ * - tool handlers 由注入的 RuntimeFacade 提供(S12 接线 Claude Code、S13 Codex + 契约测试);
11
+ * - 所有宿主调用 100% 落审计(audit 钩子,复用 SessionLog)。
12
+ */
13
+ import type { ServerTransport } from '../mcp/gateway.ts';
14
+ import { normalizeHostEvent, type HostProfile } from './mappings.ts';
15
+ export declare const HOST_TOOLS: readonly ['session_start', 'session_turn', 'session_stop', 'session_status', 'session_replay', 'session_verify', 'session_export', 'session_fork', 'tool_list', 'tool_call'];
16
+ export type HostTool = (typeof HOST_TOOLS)[number];
17
+ export interface RuntimeFacade {
18
+ sessionStart(args: {
19
+ hostSessionId: string; /** M5-DEC4:fork 工件导入(跨宿主 resume 入口),缺省为普通 start */
20
+ importJsonl?: string;
21
+ }): Promise<{
22
+ sessionId: string;
23
+ }>;
24
+ sessionTurn(args: {
25
+ sessionId: string;
26
+ input: unknown;
27
+ }): Promise<{
28
+ reason: string;
29
+ steps: number;
30
+ }>;
31
+ sessionStop(args: {
32
+ sessionId: string;
33
+ }): Promise<{
34
+ reason: string;
35
+ }>;
36
+ sessionStatus(args: {
37
+ sessionId: string;
38
+ }): Promise<{
39
+ state: string;
40
+ lastReason?: string;
41
+ }>;
42
+ sessionReplay(args: {
43
+ sessionId: string;
44
+ }): Promise<{
45
+ messages: unknown[];
46
+ }>;
47
+ sessionVerify(args: {
48
+ sessionId: string;
49
+ }): Promise<{
50
+ ok: boolean;
51
+ brokenAt: number | null;
52
+ }>;
53
+ sessionExport(args: {
54
+ sessionId: string;
55
+ }): Promise<{
56
+ bundle: unknown;
57
+ }>;
58
+ /** M5-DEC4 转正:跨宿主 fork(宿主 A 侧调用,产出可迁移工件 + 宿主 B 侧确定性会话 id) */
59
+ sessionFork(args: {
60
+ sessionId: string;
61
+ targetHostId: string;
62
+ targetHostSessionId: string;
63
+ upToSeq?: number;
64
+ }): Promise<{
65
+ sessionId: string;
66
+ jsonl: string;
67
+ migrated: number;
68
+ }>;
69
+ /** 1.6-S2(D-25):声明面出站(name/description/parameters——declaredSideEffect 权限面不出站);未装配 = 空面 */
70
+ toolList(args: Record<string, never>): Promise<{
71
+ tools: Array<{
72
+ name: string;
73
+ description?: string;
74
+ parameters?: unknown;
75
+ }>;
76
+ }>;
77
+ toolCall(args: {
78
+ sessionId: string;
79
+ tool: string;
80
+ arguments?: Record<string, unknown>;
81
+ }): Promise<{
82
+ ok: boolean;
83
+ result?: unknown;
84
+ error?: string;
85
+ }>;
86
+ }
87
+ export interface HostRegistration {
88
+ hostId: string;
89
+ profile: HostProfile;
90
+ transport: ServerTransport;
91
+ }
92
+ export declare class HostGateway {
93
+ #private;
94
+ constructor(opts: {
95
+ facade: RuntimeFacade;
96
+ audit: (event: {
97
+ kind: string;
98
+ detail: Record<string, unknown>;
99
+ ts: number;
100
+ }) => void;
101
+ });
102
+ /** 宿主登记(A050001 语义扩展:重复登记显式报错) */
103
+ registerHost(reg: HostRegistration): void;
104
+ listHosts(): string[];
105
+ /** 暴露给宿主的 tool 面(契约测试数据源:两宿主调用此方法结果必须等价) */
106
+ listTools(): HostTool[];
107
+ /** 宿主到达调用分发:登记检查 → tool 分发 → 归一化 → 审计(无旁路) */
108
+ handle(hostId: string, tool: string, args: Record<string, unknown>): Promise<{
109
+ ok: boolean;
110
+ result?: unknown;
111
+ error?: string;
112
+ }>;
113
+ /** ServerTransport 装配:把 JSON-RPC tools/call 桥到 handle(S12 接 Claude Code stdio) */
114
+ bindTransport(hostId: string): void;
115
+ }
116
+ /** 归一化便捷入口:宿主事件 → CAR 事件(mappings.ts 的 gateway 侧封装,供 S12 数据流接线) */
117
+ export declare function ingestHostEvent(hostId: string, hostEvent: string, payload: Record<string, unknown>): ReturnType<typeof normalizeHostEvent>;
@@ -0,0 +1,65 @@
1
+ import { deriveSessionId, normalizeHostEvent } from './mappings.js';
2
+ export const HOST_TOOLS = [
3
+ 'session_start', 'session_turn', 'session_stop', 'session_status',
4
+ 'session_replay', 'session_verify', 'session_export', 'session_fork', 'tool_list', 'tool_call',
5
+ ];
6
+ export class HostGateway {
7
+ #hosts = new Map();
8
+ #audit;
9
+ constructor(opts) {
10
+ this.#facade = opts.facade;
11
+ this.#audit = opts.audit;
12
+ }
13
+ #facade;
14
+ /** 宿主登记(A050001 语义扩展:重复登记显式报错) */
15
+ registerHost(reg) {
16
+ if (this.#hosts.has(reg.hostId))
17
+ throw new Error(`CAR-E-HOST: hostId "${reg.hostId}" already registered(登记制)`);
18
+ this.#hosts.set(reg.hostId, reg);
19
+ }
20
+ listHosts() { return [...this.#hosts.keys()]; }
21
+ /** 暴露给宿主的 tool 面(契约测试数据源:两宿主调用此方法结果必须等价) */
22
+ listTools() { return [...HOST_TOOLS]; }
23
+ /** 宿主到达调用分发:登记检查 → tool 分发 → 归一化 → 审计(无旁路) */
24
+ async handle(hostId, tool, args) {
25
+ const host = this.#hosts.get(hostId);
26
+ if (!host) {
27
+ this.#audit({ kind: 'host-rejected', detail: { hostId, tool, reason: 'A050001' }, ts: Date.now() });
28
+ return { ok: false, error: 'CAR-A050001: unregistered host connection rejected(登记制)' };
29
+ }
30
+ if (!HOST_TOOLS.includes(tool)) {
31
+ return { ok: false, error: `unknown host tool "${tool}"(10 tool 面,step 循环不暴露)` };
32
+ }
33
+ // tool 名(snake_case MCP 约定)→ facade 方法(camelCase TS 约定)
34
+ const camel = tool.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
35
+ const fn = this.#facade[camel];
36
+ if (typeof fn !== 'function')
37
+ return { ok: false, error: `host tool "${tool}" not wired in RuntimeFacade(S12 接线)` };
38
+ try {
39
+ const result = await fn.call(this.#facade, { ...args, __hostId: hostId });
40
+ this.#audit({ kind: 'host-call', detail: { hostId, tool, normalized: tool !== 'session_start' ? undefined : deriveSessionId(hostId, String(args.hostSessionId)) }, ts: Date.now() });
41
+ return { ok: true, result };
42
+ }
43
+ catch (e) {
44
+ this.#audit({ kind: 'host-call-error', detail: { hostId, tool, error: String(e) }, ts: Date.now() });
45
+ return { ok: false, error: String(e) };
46
+ }
47
+ }
48
+ /** ServerTransport 装配:把 JSON-RPC tools/call 桥到 handle(S12 接 Claude Code stdio) */
49
+ bindTransport(hostId) {
50
+ const host = this.#hosts.get(hostId);
51
+ if (!host)
52
+ throw new Error('CAR-A050001: cannot bind unregistered host');
53
+ void host.transport;
54
+ }
55
+ }
56
+ /** 归一化便捷入口:宿主事件 → CAR 事件(mappings.ts 的 gateway 侧封装,供 S12 数据流接线) */
57
+ export function ingestHostEvent(hostId, hostEvent, payload) {
58
+ const profile = HOST_MAPPINGS_BY_ID.get(hostId);
59
+ if (!profile)
60
+ throw new Error(`CAR-A050001: host "${hostId}" not registered(归一化前必须登记)`);
61
+ return normalizeHostEvent(profile, hostEvent, payload);
62
+ }
63
+ import { HOST_MAPPINGS } from './mappings.js';
64
+ const HOST_MAPPINGS_BY_ID = new Map(HOST_MAPPINGS.map(h => [h.hostId, h]));
65
+ void 0; // 类型锚:ServerTransport 响应契约
@@ -0,0 +1,27 @@
1
+ import type { EventKind } from '../session/log.ts';
2
+ /** 跨宿主会话 id:SH- 前缀 + 24 hex(sha256 前 12 字节) */
3
+ export declare function deriveSessionId(hostId: string, hostSessionId: string): string;
4
+ export interface HostEventMapping {
5
+ /** 宿主事件名(精确匹配 v1;glob/regex v2 评估) */
6
+ hostEvent: string;
7
+ kind: EventKind;
8
+ actor: 'user' | 'model' | 'plugin' | 'runtime';
9
+ /** 宿主 payload 字段 → CAR payload 字段(浅拷贝重命名) */
10
+ payloadMap?: Record<string, string>;
11
+ }
12
+ export interface HostProfile {
13
+ hostId: string;
14
+ /** 映射表版本(契约变更须升版本 + 双轨过渡一个迭代) */
15
+ schemaVersion: 'host-mappings-v1';
16
+ mappings: HostEventMapping[];
17
+ }
18
+ /** host-mappings-v1:Claude Code / Codex 等价映射(同一接入面,两份纯数据 profile) */
19
+ export declare const HOST_MAPPINGS: HostProfile[];
20
+ export interface NormalizedEvent {
21
+ kind: EventKind;
22
+ actor: string;
23
+ payload: Record<string, unknown>;
24
+ degraded: boolean;
25
+ }
26
+ /** 事件归一化:命中映射 → 转写;未命中 → hostRaw 降级(payload 原样保留,零静默) */
27
+ export declare function normalizeHostEvent(profile: HostProfile, hostEvent: string, payload?: Record<string, unknown>): NormalizedEvent;