@lqc123qwe/car-runtime 1.9.0 → 1.11.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 (182) hide show
  1. package/README.md +17 -1
  2. package/dist/SHA256SUMS +1 -0
  3. package/dist/authz/authz.d.ts +46 -0
  4. package/dist/authz/authz.js +76 -0
  5. package/dist/car-runtime-1.11.0.tgz +0 -0
  6. package/dist/car-runtime-1.11.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 +82 -0
  44. package/dist/loop/stop.js +184 -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 +43 -0
  65. package/dist/runtime-core/chatStep.js +121 -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 +638 -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 +104 -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 +24 -0
  86. package/dist/seam.js +23 -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/authz/authz.ts +12 -0
  107. package/src/loop/stop.ts +43 -10
  108. package/src/runtime-core/chatStep.ts +22 -2
  109. package/src/runtime-core/llm.ts +96 -13
  110. package/src/runtime-core/types.ts +22 -1
  111. package/src/seam.ts +24 -0
  112. package/src/session/log.ts +1 -1
  113. package/.gitattributes +0 -11
  114. package/.github/workflows/ci.yml +0 -293
  115. package/.github/workflows/release.yml +0 -54
  116. package/adr/ADR-001.md +0 -4
  117. package/adr/ADR-002.md +0 -5
  118. package/adr/ADR-003.md +0 -35
  119. package/adr/host-onboarding.md +0 -68
  120. package/adr/mlps-audit-checklist.md +0 -41
  121. package/adr/win32-spike.md +0 -45
  122. package/docs/plugin-authoring.md +0 -82
  123. package/native/landlock-run/Makefile +0 -19
  124. package/native/landlock-run/landlock-run.c +0 -162
  125. package/scripts/contract-check.ts +0 -56
  126. package/scripts/invariant-n1.ts +0 -96
  127. package/scripts/package-check.ts +0 -29
  128. package/scripts/ptc-baseline/run-baseline.ts +0 -118
  129. package/scripts/release-artifacts.ts +0 -111
  130. package/scripts/release-pipeline.ts +0 -245
  131. package/scripts/secrets-baseline.ts +0 -33
  132. package/scripts/secrets-scan.ts +0 -56
  133. package/scripts/win32-ffi-debug.ts +0 -143
  134. package/scripts/win32-koffi-spike.ts +0 -537
  135. package/test/contract/eventmap.golden.json +0 -7
  136. package/test/fixtures/hello-plugin.ts +0 -5
  137. package/test/kernel.spec.ts +0 -136
  138. package/test/registry-e2e/run-e2e.ts +0 -213
  139. package/test/s11.spec.ts +0 -129
  140. package/test/s12.spec.ts +0 -135
  141. package/test/s13.spec.ts +0 -117
  142. package/test/s14.spec.ts +0 -117
  143. package/test/s15.spec.ts +0 -80
  144. package/test/s16.spec.ts +0 -133
  145. package/test/s17.spec.ts +0 -102
  146. package/test/s18.spec.ts +0 -81
  147. package/test/s19.spec.ts +0 -138
  148. package/test/s2.spec.ts +0 -245
  149. package/test/s20.spec.ts +0 -241
  150. package/test/s21.spec.ts +0 -231
  151. package/test/s22-m7-store-format.spec.ts +0 -135
  152. package/test/s23-m7-export-index.spec.ts +0 -189
  153. package/test/s24-signature-gate.spec.ts +0 -185
  154. package/test/s25-m8-runtime-core.spec.ts +0 -600
  155. package/test/s26-signature-closeout.spec.ts +0 -349
  156. package/test/s27-host-load-wiring.spec.ts +0 -134
  157. package/test/s28-session-index-followup.spec.ts +0 -236
  158. package/test/s29-otel-producer.spec.ts +0 -252
  159. package/test/s3.spec.ts +0 -179
  160. package/test/s30-release-artifacts.spec.ts +0 -127
  161. package/test/s31-real-model-wiring.spec.ts +0 -329
  162. package/test/s32-telemetry-mcp-ptc.spec.ts +0 -218
  163. package/test/s33-anthropic-adapter.spec.ts +0 -354
  164. package/test/s34-mcp-remote-transport.spec.ts +0 -250
  165. package/test/s35-config-ts-zod.spec.ts +0 -145
  166. package/test/s36-host-tool-surface.spec.ts +0 -250
  167. package/test/s37-projection-dedupe.spec.ts +0 -161
  168. package/test/s38-config-jiti.spec.ts +0 -116
  169. package/test/s39-otlp-span-semantics.spec.ts +0 -100
  170. package/test/s4.spec.ts +0 -169
  171. package/test/s40-metrics-cumulative.spec.ts +0 -67
  172. package/test/s41-mcp-serve-turn-span.spec.ts +0 -148
  173. package/test/s42-traceparent-propagation.spec.ts +0 -415
  174. package/test/s43-remote-http-traceparent.spec.ts +0 -262
  175. package/test/s5.spec.ts +0 -95
  176. package/test/s6.spec.ts +0 -169
  177. package/test/s7.spec.ts +0 -136
  178. package/test/s7b-baseline.spec.ts +0 -20
  179. package/test/s9.spec.ts +0 -89
  180. package/test/sandbox-escape/j17-orchestrate.sh +0 -30
  181. package/test/sandbox-escape/run-matrix.sh +0 -98
  182. package/tsconfig.json +0 -18
@@ -0,0 +1,90 @@
1
+ /**
2
+ * T-2+ · JSON Schema → TS 深度参数渲染(M5 · DEC-4 预埋项转正,原 M3-S16 评估项)
3
+ *
4
+ * 口径(M3系统设计增补 T-2 预埋 / M4演进规划 RC-3 / M5 DEC-4 用户裁决①):
5
+ * - 渲染源仍为注册表快照同一条目(声明面 = 授权面红线不变)——inputSchema 只是
6
+ * ToolBridge/McpToolDefinition 上的附加字段,不引入独立声明通道;
7
+ * - 深度转换 **fail-visible**:超深 / 不支持节点($ref、not)/ 畸形输入落 `unknown`
8
+ * 并以内联注释标记原因(禁止静默截断——N1「零静默」纪律在类型面上的延伸);
9
+ * - 输入 = MCP tools/list 的 inputSchema(JSON Schema 2020-12 常用子集)。
10
+ */
11
+ /** 递归深度上限:超过即 fail-visible 落 unknown(防 schema 爆栈与 prompt 膨胀) */
12
+ const MAX_DEPTH = 6;
13
+ function literal(v) {
14
+ return typeof v === 'string' ? JSON.stringify(v) : String(v);
15
+ }
16
+ function isObject(v) {
17
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
18
+ }
19
+ /** 属性名合法标识符则裸写,否则 JSON 字符串引号(保留字在 TS 属性位合法,无需处理) */
20
+ function propKey(name) {
21
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
22
+ }
23
+ /**
24
+ * JSON Schema 节点 → TS 类型表达式(深度递归)。
25
+ * 任何不可判定路径返回带内联原因标记的 unknown 字符串(形如 unknown + 注释)——可读、可测、不静默。
26
+ */
27
+ export function jsonSchemaToTs(schema, depth = 0) {
28
+ if (depth > MAX_DEPTH)
29
+ return 'unknown /* depth>' + MAX_DEPTH + ' */';
30
+ if (!isObject(schema))
31
+ return 'unknown /* malformed */';
32
+ // $ref 无法在无注册表上下文解析 → fail-visible(不做猜测性内联)
33
+ if (typeof schema.$ref === 'string')
34
+ return 'unknown /* unsupported:$ref */';
35
+ // const / enum → 字面量联合(优先于 type)
36
+ if ('const' in schema)
37
+ return literal(schema.const);
38
+ if (Array.isArray(schema.enum) && schema.enum.length > 0)
39
+ return schema.enum.map(literal).join(' | ');
40
+ // 组合器:anyOf/oneOf → 联合;allOf → 交叉
41
+ for (const key of ['anyOf', 'oneOf']) {
42
+ if (Array.isArray(schema[key]) && schema[key].length > 0) {
43
+ return schema[key].map((s) => jsonSchemaToTs(s, depth + 1)).join(' | ');
44
+ }
45
+ }
46
+ if (Array.isArray(schema.allOf) && schema.allOf.length > 0) {
47
+ return schema.allOf.map((s) => jsonSchemaToTs(s, depth + 1)).join(' & ');
48
+ }
49
+ if ('not' in schema)
50
+ return 'unknown /* unsupported:not */';
51
+ // type 可能是单值或数组(['string','null'] → 联合)
52
+ const types = Array.isArray(schema.type) ? schema.type : typeof schema.type === 'string' ? [schema.type] : [];
53
+ if (types.includes('object') || 'properties' in schema || 'additionalProperties' in schema) {
54
+ const props = isObject(schema.properties) ? schema.properties : {};
55
+ const required = Array.isArray(schema.required) ? schema.required.filter((r) => typeof r === 'string') : [];
56
+ const parts = [];
57
+ for (const [name, sub] of Object.entries(props)) {
58
+ const opt = required.includes(name) ? '' : '?';
59
+ parts.push(`${propKey(name)}${opt}: ${jsonSchemaToTs(sub, depth + 1)}`);
60
+ }
61
+ const addl = schema.additionalProperties;
62
+ if (isObject(addl)) {
63
+ parts.push(`[k: string]: ${jsonSchemaToTs(addl, depth + 1)}`);
64
+ }
65
+ else if (parts.length === 0) {
66
+ // {} / { additionalProperties: true } / 空 object → 任意键值(TS 语法要求 index signature 兜底)
67
+ return 'Record<string, unknown>';
68
+ }
69
+ return `{ ${parts.join('; ')} }`;
70
+ }
71
+ if (types.includes('array') || 'items' in schema || 'prefixItems' in schema) {
72
+ if (Array.isArray(schema.prefixItems) && schema.prefixItems.length > 0) {
73
+ return `[${schema.prefixItems.map((s) => jsonSchemaToTs(s, depth + 1)).join(', ')}]`;
74
+ }
75
+ if ('items' in schema) {
76
+ // 2020-12:items 为单 schema(draft-07 数组形态按 tuple 兼容处理)
77
+ if (Array.isArray(schema.items)) {
78
+ return `[${schema.items.map((s) => jsonSchemaToTs(s, depth + 1)).join(', ')}]`;
79
+ }
80
+ return `${jsonSchemaToTs(schema.items, depth + 1)}[]`;
81
+ }
82
+ return 'unknown[]';
83
+ }
84
+ if (types.length > 0) {
85
+ const mapped = types.map((t) => (t === 'integer' ? 'number' : t === 'string' ? 'string' : t === 'number' ? 'number' : t === 'boolean' ? 'boolean' : t === 'null' ? 'null' : 'unknown /* unsupported:type:' + String(t) + ' */'));
86
+ return [...new Set(mapped)].join(' | ');
87
+ }
88
+ // 无 type / 组合器的开放 schema(true、{})→ unknown
89
+ return 'unknown /* open */';
90
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * T-2 · PTC TS SDK 渲染(M3-S15)+ 深度参数渲染转正(M5 · DEC-4)
3
+ *
4
+ * 口径(M3系统设计增补 T-2 / dsh 一手口径 tools/src/index.ts:26,97):
5
+ * - SDK = 从工具注册表快照渲染进 system prompt 的 TS 声明(**非 .d.ts 文件生成**);
6
+ * - **声明面/授权面一致性红线**:渲染源与授权消费源必须同一 Map 快照——模型看到的工具集合
7
+ * 即授权门放行的集合(多渲一个 = 无授权执行面,少渲一个 = 授权白给);
8
+ * - 参数形状优先级:paramHint(手写 TS 字面量)> inputSchema(JSON Schema 深度转换,
9
+ * M5 DEC-4 转正,见 ./schemaRender.ts)> `args: unknown`(无形状声明时的 fail-visible 默认)。
10
+ */
11
+ import type { ToolBridge } from './runCode.ts';
12
+ export interface SdkToolDef {
13
+ name: string;
14
+ description?: string;
15
+ /** 参数形状提示(TS 字面量),如 '{ x: number; y: number }'——手写声明,优先级最高 */
16
+ paramHint?: string;
17
+ /** JSON Schema(MCP tools/list inputSchema)——深度转换渲染,paramHint 缺省时生效 */
18
+ inputSchema?: unknown;
19
+ }
20
+ /** 渲染 SDK 声明:注入 PTC 程序体上文的 TS 类型面(模型据此写代码) */
21
+ export declare function renderSdk(defs: Iterable<SdkToolDef>): string;
22
+ /** 从 ToolBridge 注册表快照渲染(包装:接受可选 description/paramHint/inputSchema 的注册表条目) */
23
+ export declare function renderSdkFromRegistry(registry: Map<string, ToolBridge & {
24
+ description?: string;
25
+ paramHint?: string;
26
+ inputSchema?: unknown;
27
+ }>): string;
@@ -0,0 +1,23 @@
1
+ import { jsonSchemaToTs } from './schemaRender.js';
2
+ /** 渲染单工具参数形状(优先级:paramHint > inputSchema 深渲 > unknown) */
3
+ function renderArgsHint(d) {
4
+ if (d.paramHint)
5
+ return d.paramHint;
6
+ if (d.inputSchema !== undefined)
7
+ return jsonSchemaToTs(d.inputSchema);
8
+ return 'args: unknown';
9
+ }
10
+ /** 渲染 SDK 声明:注入 PTC 程序体上文的 TS 类型面(模型据此写代码) */
11
+ export function renderSdk(defs) {
12
+ const lines = ['// CAR PTC SDK —— 以下声明与授权门消费同一注册表快照(声明面=授权面)', 'declare const tools: {'];
13
+ for (const d of defs) {
14
+ const hint = renderArgsHint(d);
15
+ lines.push(` ${JSON.stringify(d.name)}: (${hint}) => Promise<unknown>${d.description ? ` // ${d.description}` : ''}`);
16
+ }
17
+ lines.push('};');
18
+ return lines.join('\n');
19
+ }
20
+ /** 从 ToolBridge 注册表快照渲染(包装:接受可选 description/paramHint/inputSchema 的注册表条目) */
21
+ export function renderSdkFromRegistry(registry) {
22
+ return renderSdk([...registry.entries()].map(([name, d]) => ({ name, description: d.description, paramHint: d.paramHint, inputSchema: d.inputSchema })));
23
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,71 @@
1
+ /**
2
+ * PTC worker 入口(M3-S14):隔离执行 PTC 程序体
3
+ *
4
+ * 协议:
5
+ * - workerData: { code, budget };
6
+ * - 程序体 = async 函数体(顶层 await/return 支持,dsh 一手口径);
7
+ * - tools proxy:await tools.<name>(args) → postMessage {type:'tool'} → 主线程执行 → {type:'tool-result'} 回传;
8
+ * - 预算:maxWallMs 计时超限 → done(error='budget-exceeded (maxWallMs)');maxOutputBytes 序列化后检查;
9
+ * - done 消息单发即退——程序体 = 一次原子 toolCall(收口点唯一在 executor 返回);
10
+ * - 1.8-D31:done 加法 span 字段(程序窗口 startMs/endMs——worker 侧 Date.now 计时,
11
+ * worker_threads 同进程同时钟域;主线程 relay 收口 car.ptc span——单 exporter 纪律,
12
+ * 本隔离体零独立出站通道;程序未启动〔wallTimer 抢先〕不产 span 字段)。
13
+ */
14
+ import { parentPort, workerData } from 'node:worker_threads';
15
+ const port = parentPort;
16
+ const pending = new Map();
17
+ port.on('message', (m) => {
18
+ if (m.type === 'tool-result' && m.callId) {
19
+ const p = pending.get(m.callId);
20
+ if (!p)
21
+ return;
22
+ pending.delete(m.callId);
23
+ if (m.error)
24
+ p.reject(new Error(m.error));
25
+ else
26
+ p.resolve(m.result);
27
+ }
28
+ });
29
+ const tools = new Proxy({}, {
30
+ get: (_, name) => (args) => new Promise((resolve, reject) => {
31
+ const callId = Math.random().toString(36).slice(2);
32
+ pending.set(callId, { resolve, reject });
33
+ port.postMessage({ type: 'tool', callId, name, args });
34
+ }),
35
+ });
36
+ const budget = workerData.budget;
37
+ // 1.8-D31:程序窗口计时(null = 程序未启动——wallTimer 抢先不产 span 字段)
38
+ let spanStart = null;
39
+ const finish = (msg) => {
40
+ port.postMessage({ ...msg, ...(spanStart !== null ? { span: { startMs: spanStart, endMs: Date.now() } } : {}) });
41
+ setTimeout(() => process.exit(0), 50); // 让消息冲刷
42
+ };
43
+ const wallTimer = setTimeout(() => finish({ type: 'done', error: `budget-exceeded (maxWallMs=${budget.maxWallMs})——预算到期为资源护栏,不掐 turn(工具错误结果交回模型)` }), budget.maxWallMs);
44
+ void (async () => {
45
+ try {
46
+ const code = workerData.code;
47
+ // erasable-only 的执行端:eval 前剥离类型(Node 22.13+ 内置 stripTypeScriptTypes)——
48
+ // 程序体是 erasable TS(含类型注解),new Function 是纯 JS 求值器,不剥离则注解即 SyntaxError
49
+ const { stripTypeScriptTypes } = await import('node:module');
50
+ // stripTypeScriptTypes 以「模块」语义解析——片段含顶层 return 会报 ERR_INVALID_TYPESCRIPT_SYNTAX;
51
+ // 故先包成 export default 箭头函数再剥离,剥离后还原为 return 表达式(程序体含字面 export default 不受支持,见 erasable 禁项)
52
+ const wrapped = 'export default async () => {\n' + code + '\n}';
53
+ const js = stripTypeScriptTypes(wrapped, { mode: 'strip' });
54
+ const body = js.replace(/^export default /, 'return ');
55
+ // 程序体 = async 函数体(顶层 await/return);tools 为受控 proxy(仅显式调用,无宿主 import 面)
56
+ const fn = new Function('tools', '"use strict";\n' + body + '\n');
57
+ spanStart = Date.now();
58
+ const result = await fn(tools)();
59
+ clearTimeout(wallTimer);
60
+ const out = JSON.stringify(result ?? null);
61
+ if (Buffer.byteLength(out) > budget.maxOutputBytes) {
62
+ finish({ type: 'done', error: `budget-exceeded (maxOutputBytes=${budget.maxOutputBytes})` });
63
+ return;
64
+ }
65
+ finish({ type: 'done', result: JSON.parse(out) });
66
+ }
67
+ catch (e) {
68
+ clearTimeout(wallTimer);
69
+ finish({ type: 'done', error: String(e) });
70
+ }
71
+ })();
@@ -0,0 +1,4 @@
1
+ {"stage":"archive","detail":"git archive HEAD → car-runtime-1.11.0.tgz(328238 bytes,树哈希锚定)","ts":1791363366364}
2
+ {"stage":"sha256sums","detail":"SHA256SUMS e33ef93a776d2910…(裸字节复核通过)","ts":1791363366365}
3
+ {"stage":"sbom","detail":"51 个组件(运行时依赖 0 项)","ts":1791363366373}
4
+ {"stage":"cosign-keyless","detail":"signed car-runtime-1.11.0.tgz (keyless OIDC + Rekor)","ts":1791363368159}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * M8 · SQ-07 模型接入集成(chat → 流式消费 → assistant 落 M7 → ModelStep 交 M4 状态机)
3
+ *
4
+ * 时序对齐(§3.2.M8.4 SQ-07 逐行):
5
+ * 1. 请求前断言:assertModelVisibleLogged(配合 M7,N1 不变量前置);
6
+ * 2. 请求消息 = log.deriveMessages() 投影——Model-visible means logged 由构造保证
7
+ * (本层不引入任何未落链的模型可见输入:新增可见输入必须新增事件类型,§3.5.5 红线;
8
+ * system prompt 组装属 M4/M2 职责,v1 不在本层旁路);
9
+ * 3. 凭据解析在适配器内(resolve→reveal,A080001 在首块前抛出 → runTurn catch → turn error,
10
+ * 用户文案引导 car doctor);
11
+ * 4. 流式消费:delta 过 StreamRedactor(跨 chunk 命中不泄漏)+ toolCallDelta 聚合
12
+ * (半截 JSON 不解析——ADR-001 同纪律,length 时只交 truncatedTools);
13
+ * 5. assistant 文本事件落 M7(脱敏后 + secretsRedacted 计数留痕 meta);
14
+ * 6. finishReason 不可变透传为 ModelStep.stopReason(error/aborted → 'error',BD-04 收口
15
+ * 由 runTurn 落 turnEnd);工具调用事件由 runTurn executeBatch 落(本层不重复);
16
+ * 7. 1.11-S2(D-33 / 计量设计 §3.3):流尾 usage chunk → 'runtime'/'usage' 请求级事实落链
17
+ * (缺失 = provider 未回/流中断/畸形,禁补造;actor='runtime'、model_visible=0 不进模型
18
+ * 消息流)——turn 级聚合在 stop.ts 收口单点,本层不做跨请求状态。
19
+ */
20
+ import type { SessionLog } from '../session/log.ts';
21
+ import { type ModelStep } from '../loop/stop.ts';
22
+ import type { RuntimeCore } from './llm.ts';
23
+ import type { ToolDefinition } from './types.ts';
24
+ export interface ChatStepOptions {
25
+ core: RuntimeCore;
26
+ log: SessionLog;
27
+ turnId: string;
28
+ /** 模型名(provider 侧路由) */
29
+ model: string;
30
+ /** 工具声明面(ToolDefinition 含 declaredSideEffect;不出站权限面仅 schema) */
31
+ tools: ToolDefinition[];
32
+ maxTokens?: number;
33
+ adapterId?: string;
34
+ /** 1.4-S4(D-19):turn 取消信号——chunk 间隙与流结束后(finish 检查前)检查,命中抛 TurnAborted */
35
+ signal?: {
36
+ aborted: boolean;
37
+ };
38
+ }
39
+ /** ModelStep 加法扩展:脱敏计数留痕(S15 secretsRedacted 同名口径) */
40
+ export type ChatStepResult = ModelStep & {
41
+ secretsRedacted: number;
42
+ };
43
+ export declare function chatStep(opts: ChatStepOptions): Promise<ChatStepResult>;
@@ -0,0 +1,121 @@
1
+ import { TurnAborted } from '../loop/stop.js';
2
+ import { StreamRedactor } from './redaction.js';
3
+ import { scanSecrets } from '../security/secrets.js';
4
+ import { providerUnreachable } from './errors.js';
5
+ export async function chatStep(opts) {
6
+ const { core, log, turnId, model, tools, maxTokens, adapterId } = opts;
7
+ // 1. 请求前断言(SQ-07 #2):N1 失守 = 带病请求,显式拒绝
8
+ const asserted = log.assertModelVisibleLogged();
9
+ if (!asserted.ok) {
10
+ throw new Error(`CAR-E-N1: 模型请求前快照与日志投影失配 @ atSeq=${asserted.failedAt}(Model-visible means logged 违规)`);
11
+ }
12
+ // 2. 请求消息 = 投影(角色系统:user/assistant/toolResult 与 LlmRequest 对齐);
13
+ // toolCall 事件投影形态为 { role:'assistant', toolCall }(log.ts 口径)——此处归一化为
14
+ // content {toolCall} 载荷(适配器历史回路映射的唯一消费源)。
15
+ // 1.5-BUG-1:此前仅透传 m.content,toolCall 属性被丢弃——工具调用历史在后续请求
16
+ // 静默消失(真实 provider 对无 tool_calls 前驱的 tool 消息直接 400);anthropic E2E
17
+ // 生产调用点核查(1.5-GO-5)暴露,openai-compat 同样受益。
18
+ const messages = log.deriveMessages().map(m => ({
19
+ role: m.role,
20
+ content: m.toolCall !== undefined
21
+ ? { toolCall: m.toolCall }
22
+ : m.content,
23
+ }));
24
+ // 4. 流式消费(3. 凭据解析在适配器首块前,A080001 由此传播)
25
+ const redactor = new StreamRedactor();
26
+ let text = '';
27
+ const calls = new Map();
28
+ let stopReason;
29
+ let errorDetail;
30
+ let usage;
31
+ const stream = core.chat({
32
+ model,
33
+ messages,
34
+ tools,
35
+ ...(maxTokens != null ? { maxTokens } : {}),
36
+ ...(adapterId ? { adapterId } : {}),
37
+ ...(opts.signal ? { signal: opts.signal } : {}),
38
+ ...(opts.log.sessionId ? { metadata: { sessionId: opts.log.sessionId, turnId, traceId: `${opts.log.sessionId}:${turnId}` } } : {}),
39
+ });
40
+ for await (const chunk of stream) {
41
+ // 1.4-S4(D-19):chunk 间隙取消——此刻 assistant 尚未落链(流完才 append),无半截消息
42
+ if (opts.signal?.aborted)
43
+ throw new TurnAborted();
44
+ if (chunk.delta !== undefined)
45
+ text += redactor.push(chunk.delta);
46
+ if (chunk.toolCallDelta) {
47
+ const idx = chunk.toolCallDelta.index ?? 0;
48
+ const cur = calls.get(idx) ?? { argsBuf: '' };
49
+ if (chunk.toolCallDelta.id)
50
+ cur.id = chunk.toolCallDelta.id;
51
+ if (chunk.toolCallDelta.name)
52
+ cur.name = chunk.toolCallDelta.name;
53
+ if (chunk.toolCallDelta.argumentsDelta)
54
+ cur.argsBuf += chunk.toolCallDelta.argumentsDelta;
55
+ calls.set(idx, cur);
56
+ }
57
+ if (chunk.error)
58
+ errorDetail = chunk.error.message;
59
+ if (chunk.usage)
60
+ usage = chunk.usage; // D-33:附着于最后一个 chunk——后到覆盖
61
+ if (chunk.finishReason !== undefined)
62
+ stopReason = chunk.finishReason === 'aborted' ? 'error' : chunk.finishReason;
63
+ }
64
+ // 1.4-S4(D-19):finish 检查前先查取消(适配器取消路径静默收口无 finishReason——
65
+ // 必须先于此处的契约兜底,否则取消被误判为适配器违规)
66
+ if (opts.signal?.aborted)
67
+ throw new TurnAborted();
68
+ if (stopReason === undefined) {
69
+ // 适配器层已保证首 finishReason(AL-05);防御性兜底显式化
70
+ throw new Error('CAR-E-LLM-FINISH: 流结束无 finishReason(适配器契约违规)');
71
+ }
72
+ text += redactor.flush();
73
+ // 1.11-S2(D-33 / 计量设计 §3.3):请求级 usage 事实落链(拿到 usage 的最后一个 chunk 即请求
74
+ // 收尾;撞限/异常 turn 照常落——截断前已完成请求的 usage 事实不吞)。缺失 = provider 未回,
75
+ // 禁补造;actor='runtime'、不进模型消息流(deriveMessages 不投影 usage,§2.5 红线)
76
+ if (usage) {
77
+ log.append('runtime', 'usage', turnId, {
78
+ model: usage.model,
79
+ adapterId: usage.adapterId,
80
+ input: usage.inputTokens,
81
+ output: usage.outputTokens,
82
+ cacheRead: usage.cacheReadTokens,
83
+ cacheCreation: usage.cacheCreationTokens,
84
+ providerRaw: usage.providerRaw,
85
+ });
86
+ }
87
+ // 5. assistant 文本落 M7(脱敏后 + 计数留痕;空文本不产事件)
88
+ // flush 后兜底复扫只计数不复写:流式遮蔽已生效,>0 即流式边界逃逸的审计信号
89
+ let secretsRedacted = 0;
90
+ if (text.length) {
91
+ secretsRedacted = scanSecrets(text).hits.length;
92
+ log.append('model', 'assistant', turnId, text, { secretsRedacted });
93
+ }
94
+ // 6. finishReason → ModelStep(不可变透传;length 不解析 args——ADR-001 解析前收口)
95
+ if (stopReason === 'length') {
96
+ const truncated = [...calls.values()].filter(c => c.id || c.name).map(c => ({ id: c.id ?? `tc-${c.name ?? 'unknown'}`, tool: c.name ?? 'unknown' }));
97
+ return { stopReason: 'length', ...(text ? { text } : {}), truncatedTools: truncated, secretsRedacted };
98
+ }
99
+ if (stopReason === 'toolUse') {
100
+ const toolCalls = [];
101
+ for (const [idx, c] of [...calls.entries()].sort((a, b) => a[0] - b[0])) {
102
+ if (!c.name)
103
+ continue;
104
+ let args;
105
+ try {
106
+ args = c.argsBuf ? JSON.parse(c.argsBuf) : {};
107
+ }
108
+ catch {
109
+ // toolUse 声明下 args 非法 JSON = 协议违规显式化(非 length 场景无 truncatedTools 口径)
110
+ throw new Error(`CAR-E-LLM-PROTO: 工具调用 ${c.name} 参数非法 JSON(finishReason=${stopReason},${errorDetail ?? '无附加错误'})`);
111
+ }
112
+ toolCalls.push({ id: c.id ?? `tc-${idx}-${c.name}`, tool: c.name, args });
113
+ }
114
+ return { stopReason: 'toolUse', ...(text ? { text } : {}), ...(toolCalls.length ? { toolCalls } : {}), secretsRedacted };
115
+ }
116
+ // error(含 aborted 映射):以 B080001 抛出 → runTurn catch → turnEnd error 携带 detail(BD-04 收口)
117
+ if (stopReason === 'error') {
118
+ throw providerUnreachable(errorDetail ?? 'provider 返回 error 终止(无附加说明)');
119
+ }
120
+ return { stopReason: 'stop', ...(text ? { text } : {}), secretsRedacted };
121
+ }
@@ -0,0 +1,49 @@
1
+ import type { CredentialRef, CredentialSource } from './types.ts';
2
+ export interface CredentialResolveOptions {
3
+ /** 测试注入;缺省取 process.env */
4
+ env?: NodeJS.ProcessEnv;
5
+ /** env fallback 显式开启(规格要求默认关;CAR_ALLOW_ENV_CREDENTIALS=1 等效) */
6
+ allowEnvFallback?: boolean;
7
+ /**
8
+ * keychain 读取器注入(测试替身):入参 keychain 服务名,返回明文或 null(不存在)。
9
+ * 缺省用平台命令真实读取;平台无读通道(win32)= 通道不可用(显式降级)。
10
+ */
11
+ keychainReader?: (service: string) => string | null;
12
+ /** reveal 审计回调(值不入参——审计全量留痕但零明文) */
13
+ audit?: (event: {
14
+ action: 'resolve' | 'reveal';
15
+ provider: string;
16
+ source: CredentialSource;
17
+ origin: string;
18
+ }) => void;
19
+ }
20
+ /** provider 候选 env 变量(doctor 存在性探测等诊断面复用;值永不打印) */
21
+ export declare function providerEnvVars(provider: string): string[];
22
+ /** keychain 服务名(命名空间隔离:car-runtime/<provider>) */
23
+ export declare function keychainService(provider: string): string;
24
+ interface KeychainChannel {
25
+ reader: ((service: string) => string | null) | null;
26
+ note: string;
27
+ }
28
+ /** 平台 keychain 通道探测:命令在场(darwin/linux)→ 真实读取器;无读通道(win32)→ 显式 null */
29
+ export declare function probeKeychainChannel(platform?: NodeJS.Platform): KeychainChannel;
30
+ export declare class CredentialService {
31
+ #private;
32
+ constructor(opts?: {
33
+ keychainReader?: (service: string) => string | null;
34
+ platform?: NodeJS.Platform;
35
+ });
36
+ /** keychain 通道状态(doctor 展示/测试断言;缺席显式登记不静默) */
37
+ get channelStatus(): {
38
+ available: boolean;
39
+ note: string;
40
+ };
41
+ /**
42
+ * 解析凭据引用(天然幂等;返回值不含明文)。
43
+ * 检索顺序:keychain →(显式开启时)env;全落空 = A080001。
44
+ */
45
+ resolve(provider: string, opts?: CredentialResolveOptions): CredentialRef;
46
+ /** 按引用取明文(仅 provider 适配器请求时点调用;每次经审计留痕,值不入日志不入错误) */
47
+ reveal(ref: CredentialRef, opts?: CredentialResolveOptions): string;
48
+ }
49
+ export {};
@@ -0,0 +1,118 @@
1
+ /**
2
+ * M8 · 凭据门(resolveCredential ——《系统设计》§3.2.M8.2 凭据行 / SQ-07 凭据解析 / O-13)
3
+ *
4
+ * 口径:
5
+ * - 解析链:OS Keychain(C-03)→ env fallback(**需用户显式开启**——§3.2.M8.3 字段表
6
+ * 「env 需用户显式开启 fallback」);全落空 = CarM8Error A080001(不重试,引导 car doctor);
7
+ * - Keychain 通道按平台探测(能力探测三件套:命令在场的显式探测,缺席显式降级不静默):
8
+ * darwin = `security find-generic-password`、linux = `secret-tool`、
9
+ * win32 = 零依赖读通道缺席(cmdkey 仅列举不可读值)→ 显式降级登记;
10
+ * - CredentialRef 不落明文(O-13):ref 只含 provider/source/origin;明文经 reveal() 按需取用,
11
+ * reveal 必须经审计回调(provider+source 留痕,值不入审计——值入日志即泄露面,secrets 红线);
12
+ * - 环境变量约定:OPENAI_API_KEY(openai-compat)/ ANTHROPIC_API_KEY(anthropic)/
13
+ * CAR_LLM_API_KEY(通用兜底);显式开启 = opts.allowEnvFallback 或 CAR_ALLOW_ENV_CREDENTIALS=1。
14
+ */
15
+ import { spawnSync } from 'node:child_process';
16
+ import { CarM8Error, credentialMissing } from './errors.js';
17
+ /** provider → 候选 env 变量(按序检索;CAR_LLM_API_KEY 为通用兜底) */
18
+ const PROVIDER_ENV_VARS = {
19
+ 'openai-compat': ['OPENAI_API_KEY'],
20
+ anthropic: ['ANTHROPIC_API_KEY'],
21
+ };
22
+ const GENERIC_ENV_VARS = ['CAR_LLM_API_KEY'];
23
+ /** provider 候选 env 变量(doctor 存在性探测等诊断面复用;值永不打印) */
24
+ export function providerEnvVars(provider) {
25
+ return [...(PROVIDER_ENV_VARS[provider] ?? []), ...GENERIC_ENV_VARS];
26
+ }
27
+ /** keychain 服务名(命名空间隔离:car-runtime/<provider>) */
28
+ export function keychainService(provider) {
29
+ return `car-runtime/${provider}`;
30
+ }
31
+ /** 平台 keychain 通道探测:命令在场(darwin/linux)→ 真实读取器;无读通道(win32)→ 显式 null */
32
+ export function probeKeychainChannel(platform = process.platform) {
33
+ if (platform === 'darwin') {
34
+ return { reader: service => {
35
+ const r = spawnSync('security', ['find-generic-password', '-s', service, '-w'], { encoding: 'utf-8', timeout: 5000 });
36
+ if (r.status !== 0 || r.stdout == null)
37
+ return null;
38
+ return r.stdout.replace(/\r?\n$/, '') || null;
39
+ }, note: 'darwin security' };
40
+ }
41
+ if (platform === 'linux') {
42
+ return { reader: service => {
43
+ const r = spawnSync('secret-tool', ['lookup', 'service', service], { encoding: 'utf-8', timeout: 5000 });
44
+ if (r.status !== 0 || r.stdout == null)
45
+ return null;
46
+ return r.stdout.replace(/\r?\n$/, '') || null;
47
+ }, note: 'linux secret-tool' };
48
+ }
49
+ // win32 及其他:无零依赖读通道——显式不可用(禁静默假装有 keychain)
50
+ return { reader: null, note: `keychain 读通道在 ${platform} 缺席(零依赖实现无 OS 凭据库读取面)——显式降级` };
51
+ }
52
+ export class CredentialService {
53
+ #channel;
54
+ #platform;
55
+ constructor(opts = {}) {
56
+ this.#platform = opts.platform ?? process.platform;
57
+ this.#channel = opts.keychainReader
58
+ ? { reader: opts.keychainReader, note: 'injected reader' }
59
+ : probeKeychainChannel(this.#platform);
60
+ }
61
+ /** keychain 通道状态(doctor 展示/测试断言;缺席显式登记不静默) */
62
+ get channelStatus() {
63
+ return { available: this.#channel.reader !== null, note: this.#channel.note };
64
+ }
65
+ /**
66
+ * 解析凭据引用(天然幂等;返回值不含明文)。
67
+ * 检索顺序:keychain →(显式开启时)env;全落空 = A080001。
68
+ */
69
+ resolve(provider, opts = {}) {
70
+ const env = opts.env ?? process.env;
71
+ const searched = [];
72
+ const service = keychainService(provider);
73
+ if (this.#channel.reader) {
74
+ searched.push(`keychain:${service}`);
75
+ const value = this.#channel.reader(service);
76
+ if (value) {
77
+ opts.audit?.({ action: 'resolve', provider, source: 'keychain', origin: service });
78
+ return { provider, source: 'keychain', origin: service };
79
+ }
80
+ }
81
+ else {
82
+ searched.push(`keychain:${service}(通道缺席)`);
83
+ }
84
+ const allowEnv = opts.allowEnvFallback === true || env.CAR_ALLOW_ENV_CREDENTIALS === '1';
85
+ if (allowEnv) {
86
+ const candidates = providerEnvVars(provider);
87
+ for (const name of candidates) {
88
+ searched.push(`env:${name}`);
89
+ if (env[name]) {
90
+ opts.audit?.({ action: 'resolve', provider, source: 'env', origin: name });
91
+ return { provider, source: 'env', origin: name, degraded: this.#channel.reader ? undefined : this.#channel.note };
92
+ }
93
+ }
94
+ }
95
+ else {
96
+ searched.push('env(未显式开启 fallback)');
97
+ }
98
+ throw credentialMissing(provider, searched);
99
+ }
100
+ /** 按引用取明文(仅 provider 适配器请求时点调用;每次经审计留痕,值不入日志不入错误) */
101
+ reveal(ref, opts = {}) {
102
+ const env = opts.env ?? process.env;
103
+ if (ref.source === 'keychain') {
104
+ if (!this.#channel.reader)
105
+ throw new CarM8Error('A080001', 'CAR-E-CRED-MISSING', `keychain 通道缺席,无法取值(${ref.origin})`, '未找到模型服务凭据,请运行 car doctor', false);
106
+ const value = this.#channel.reader(ref.origin);
107
+ if (!value)
108
+ throw credentialMissing(ref.provider, [`keychain:${ref.origin}(resolve 后消失)`]);
109
+ opts.audit?.({ action: 'reveal', provider: ref.provider, source: ref.source, origin: ref.origin });
110
+ return value;
111
+ }
112
+ const value = env[ref.origin];
113
+ if (!value)
114
+ throw credentialMissing(ref.provider, [`env:${ref.origin}(resolve 后消失)`]);
115
+ opts.audit?.({ action: 'reveal', provider: ref.provider, source: ref.source, origin: ref.origin });
116
+ return value;
117
+ }
118
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * M8 · 错误面(《系统设计》§3.5.1 六位错误码 + 代码库 CAR-E-* 前缀双轨)
3
+ *
4
+ * - A080001 凭据缺失:不重试,用户文案引导 car doctor(SQ-07 凭据缺失分支);
5
+ * - B080001 LLM Provider 不可达/重试耗尽:退避后重试(§3.5.4 基线——2 次指数退避 1s/2s);
6
+ * - 两条均为「错误路径不改写 finishReason 语义」的守门错误:流中途失败以
7
+ * finishReason='error' chunk 收口(BD-04),首块前失败以 CarError 抛出(runTurn catch → turn error)。
8
+ */
9
+ export type M8ErrorCode = 'A080001' | 'B080001';
10
+ export declare class CarM8Error extends Error {
11
+ /** 六位错误码(§3.5.1 注册表) */
12
+ readonly code: M8ErrorCode;
13
+ /** CAR-E-* 代码库前缀(与 CAR-E-DEPCYCLE 等同风格) */
14
+ readonly slug: string;
15
+ /** 用户文案(§3.5.1 注册表原文) */
16
+ readonly userHint?: string | undefined;
17
+ /** 重试建议(A080001 不重试 / B080001 退避后重试) */
18
+ readonly retryable: boolean;
19
+ constructor(
20
+ /** 六位错误码(§3.5.1 注册表) */
21
+ code: M8ErrorCode,
22
+ /** CAR-E-* 代码库前缀(与 CAR-E-DEPCYCLE 等同风格) */
23
+ slug: string, message: string,
24
+ /** 用户文案(§3.5.1 注册表原文) */
25
+ userHint?: string | undefined,
26
+ /** 重试建议(A080001 不重试 / B080001 退避后重试) */
27
+ retryable?: boolean);
28
+ }
29
+ export declare function credentialMissing(provider: string, searched: string[]): CarM8Error;
30
+ export declare function providerUnreachable(detail: string): CarM8Error;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * M8 · 错误面(《系统设计》§3.5.1 六位错误码 + 代码库 CAR-E-* 前缀双轨)
3
+ *
4
+ * - A080001 凭据缺失:不重试,用户文案引导 car doctor(SQ-07 凭据缺失分支);
5
+ * - B080001 LLM Provider 不可达/重试耗尽:退避后重试(§3.5.4 基线——2 次指数退避 1s/2s);
6
+ * - 两条均为「错误路径不改写 finishReason 语义」的守门错误:流中途失败以
7
+ * finishReason='error' chunk 收口(BD-04),首块前失败以 CarError 抛出(runTurn catch → turn error)。
8
+ */
9
+ export class CarM8Error extends Error {
10
+ code;
11
+ slug;
12
+ userHint;
13
+ retryable;
14
+ constructor(
15
+ /** 六位错误码(§3.5.1 注册表) */
16
+ code,
17
+ /** CAR-E-* 代码库前缀(与 CAR-E-DEPCYCLE 等同风格) */
18
+ slug, message,
19
+ /** 用户文案(§3.5.1 注册表原文) */
20
+ userHint,
21
+ /** 重试建议(A080001 不重试 / B080001 退避后重试) */
22
+ retryable = false) {
23
+ super(`${slug}: ${message}(${code})`);
24
+ this.code = code;
25
+ this.slug = slug;
26
+ this.userHint = userHint;
27
+ this.retryable = retryable;
28
+ this.name = 'CarM8Error';
29
+ }
30
+ }
31
+ export function credentialMissing(provider, searched) {
32
+ return new CarM8Error('A080001', 'CAR-E-CRED-MISSING', `未找到模型服务凭据(provider=${provider},已检索:${searched.join('、') || '无可用通道'})`, '未找到模型服务凭据,请运行 car doctor', false);
33
+ }
34
+ export function providerUnreachable(detail) {
35
+ return new CarM8Error('B080001', 'CAR-E-LLM-UNREACHABLE', `LLM Provider 不可达/重试耗尽:${detail}`, '模型服务暂时不可用,本轮已停止', true);
36
+ }