@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,196 @@
1
+ /**
2
+ * 1.5-S2 · MCP 远程传输客户端(D-21):Streamable HTTP(2025-06-18 spec 单轨)
3
+ *
4
+ * 口径:
5
+ * - CAR → 远程 MCP Server 方向(gateway.ts ClientTransport 的第二真实通道——与 stdio 并列);
6
+ * - 懒 initialize 握手:首次 send 前自动 initialize → notifications/initialized;
7
+ * 响应 `Mcp-Session-Id` 头存在则后续请求回传(无会话头的 server 容许);
8
+ * - 响应形态双兼容:application/json 单响应 / text/event-stream 逐事件取本请求 id
9
+ * (SSE 命中即收口,流显式 destroy——悬挂 socket 是 1.4-BUG-2 同源教训);
10
+ * - **出站走 node:http(s) 不用 undici fetch**(1.4-BUG-2 已验证口径:win32 退出 libuv
11
+ * 断言 0xC0000409 与 undici socket 相关,telemetry 出站同此先例);TLS 门复用
12
+ * assertTlsOrLoopback(D-12a:http 仅豁免 loopback——本地假 server 与本地 MCP);
13
+ * - send 内建 30s 超时(与 stdio 同口径;gateway BD-02 外层 race 不变);
14
+ * - headers 凭据规则(CR-05 经配置通道执行):值支持 `${ENV_VAR}` 引用(env 缺席显式报错);
15
+ * 字面值疑似明文凭据(containsPlaintextCredential 同款模式)直接拒绝——env 引用注入
16
+ * 才是登记合规通道(展开后值不复查:引注入即目的,展开值是凭据本身);
17
+ * - legacy HTTP+SSE(2024-11-05)不做(1.5 规划 D-21 登记不追溯);
18
+ * - 1.9(D-32 远程 MCP 头通道):send 加法可选第二参 opts.traceparent → tools/call 请求头
19
+ * 加法 `traceparent`(W3C Trace Context HTTP 载体惯例——第三方 OTel 接入面主消费通道);
20
+ * 注入前 parseTraceparent 校验,畸形**不注入不抛**(协议面 fail-open——严格格式天然排除
21
+ * CR/LF,兼防头注入越权面);otel 关 = 双载体零注入(头与 JSON-RPC 字节面不变);
22
+ * initialize/notifications/tools/list 不注入(无 span 上下文——1.8 tools/list 口径延伸);
23
+ * 配置 headers 含字面 `traceparent` 时注入优先(动态 span 上下文为请求级事实,登记)。
24
+ */
25
+ import { request as httpRequest } from 'node:http';
26
+ import { request as httpsRequest } from 'node:https';
27
+ import { containsPlaintextCredential } from './gateway.js';
28
+ import { assertTlsOrLoopback } from '../runtime-core/llm.js';
29
+ import { parseTraceparent } from '../runtime-core/trace.js';
30
+ export const MCP_PROTOCOL_VERSION = '2025-06-18';
31
+ /** node:http(s) POST 单发(1.4-BUG-2 口径:出站不走 undici;TLS 门由 createHttpMcpTransport 把守) */
32
+ export function rawPost(url, headers, body, timeoutMs) {
33
+ const u = new URL(url);
34
+ const send = u.protocol === 'https:' ? httpsRequest : httpRequest;
35
+ return new Promise((resolve, reject) => {
36
+ const req = send(u, { method: 'POST', headers }, res => {
37
+ resolve({
38
+ status: res.statusCode ?? 0,
39
+ headers: res.headers,
40
+ body: res,
41
+ });
42
+ });
43
+ req.setTimeout(timeoutMs, () => req.destroy(new Error(`CAR-E-MCP: http transport timeout (${timeoutMs}ms)`)));
44
+ req.on('error', reject);
45
+ req.end(body);
46
+ });
47
+ }
48
+ /**
49
+ * ${ENV_VAR} 头引用展开(D-21):值内任意位置的 `${NAME}` 均替换为 env.NAME(如
50
+ * `Bearer ${TOKEN}`);env 缺席显式报错(fail-visible,禁静默空值);字面值疑似明文凭据
51
+ * 直接拒绝(按原始值检查——展开值是凭据本身,引注入即目的)。
52
+ */
53
+ export function resolveMcpHeaders(raw, env = process.env) {
54
+ const out = {};
55
+ for (const [k, v] of Object.entries(raw)) {
56
+ const cred = containsPlaintextCredential({ [k]: v });
57
+ if (cred)
58
+ throw new Error(`CAR-E-MCP: headers.${k}——${cred}(配置明文禁令 CR-05;请改用 \`\${ENV_VAR}\` 引用注入)`);
59
+ out[k] = v.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_all, name) => {
60
+ const val = env[name];
61
+ if (val === undefined || val === '') {
62
+ throw new Error(`CAR-E-MCP: headers.${k} 引用的环境变量 ${name} 未设置(fail-visible,禁静默空值)`);
63
+ }
64
+ return val;
65
+ });
66
+ }
67
+ return out;
68
+ }
69
+ export function createHttpMcpTransport(spec) {
70
+ // D-12a TLS 门(与 LLM 适配器同款):https 直接通过;http 仅豁免 loopback
71
+ assertTlsOrLoopback(spec.url.replace(/\/+$/, ''));
72
+ const timeoutMs = spec.timeoutMs ?? 30_000;
73
+ let sessionId = null;
74
+ let initialized = false;
75
+ let initPromise = null;
76
+ let closed = false;
77
+ const baseHeaders = () => ({
78
+ 'content-type': 'application/json',
79
+ 'accept': 'application/json, text/event-stream',
80
+ ...(sessionId ? { 'mcp-session-id': sessionId } : {}),
81
+ ...spec.headers,
82
+ });
83
+ /** 流全读(JSON 响应) */
84
+ async function readAll(res) {
85
+ const parts = [];
86
+ for await (const chunk of res.body)
87
+ parts.push(Buffer.from(chunk));
88
+ return Buffer.concat(parts);
89
+ }
90
+ /** SSE 响应逐事件取本请求 id(data JSON 自带 id;命中即收口,流显式 destroy) */
91
+ async function readSseForId(res, id) {
92
+ const decoder = new TextDecoder();
93
+ let buf = '';
94
+ try {
95
+ for await (const chunk of res.body) {
96
+ buf += decoder.decode(chunk, { stream: true });
97
+ let nl;
98
+ while ((nl = buf.indexOf('\n')) >= 0) {
99
+ const line = buf.slice(0, nl).replace(/\r$/, '');
100
+ buf = buf.slice(nl + 1);
101
+ if (!line.startsWith('data:'))
102
+ continue;
103
+ const data = line.slice(5).trim();
104
+ if (!data)
105
+ continue;
106
+ let evt;
107
+ try {
108
+ evt = JSON.parse(data);
109
+ }
110
+ catch {
111
+ continue;
112
+ }
113
+ if (evt?.id === id)
114
+ return evt;
115
+ }
116
+ }
117
+ }
118
+ finally {
119
+ try {
120
+ res.body.destroy();
121
+ }
122
+ catch { /* 流已自然结束 */ }
123
+ }
124
+ throw new Error('CAR-E-MCP: SSE 流结束未见本请求响应(阶段:read-response)');
125
+ }
126
+ async function postAndRead(body, matchId, phase, extraHeaders = {}) {
127
+ // 1.9-D32:extraHeaders 展开在 baseHeaders 之后——配置头同名时注入优先(登记口径)
128
+ const res = await rawPost(spec.url, { ...baseHeaders(), ...extraHeaders }, JSON.stringify(body), timeoutMs);
129
+ const sid = res.headers['mcp-session-id'];
130
+ if (typeof sid === 'string')
131
+ sessionId = sid;
132
+ if (res.status < 200 || res.status >= 300) {
133
+ try {
134
+ res.body.destroy();
135
+ }
136
+ catch { /* 已结束 */ }
137
+ throw new Error(`CAR-E-MCP: HTTP ${res.status}(阶段:${phase}——server 拒绝或不可达)`);
138
+ }
139
+ const ct = String(res.headers['content-type'] ?? '');
140
+ if (ct.includes('text/event-stream'))
141
+ return readSseForId(res, matchId ?? Number.NaN);
142
+ const json = JSON.parse((await readAll(res)).toString('utf-8'));
143
+ return json;
144
+ }
145
+ /** 懒 initialize 握手(首次 send 前恰好一次):initialize → 会话头捕获 → notifications/initialized */
146
+ async function doInitialize() {
147
+ const initRes = await postAndRead({
148
+ jsonrpc: '2.0', id: -1, method: 'initialize',
149
+ params: { protocolVersion: MCP_PROTOCOL_VERSION, capabilities: {}, clientInfo: { name: 'car-runtime', version: '0.0.0' } },
150
+ }, -1, 'initialize');
151
+ if (initRes.error) {
152
+ throw new Error(`CAR-E-MCP: initialize 被拒:${initRes.error.message}(阶段:initialize)`);
153
+ }
154
+ // notifications/initialized:无 id 通知——2xx/202 即视为送达(无响应体,显式丢弃)
155
+ const res = await rawPost(spec.url, baseHeaders(), JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' }), timeoutMs);
156
+ try {
157
+ res.body.destroy();
158
+ }
159
+ catch { /* 已结束 */ }
160
+ if (res.status < 200 || res.status >= 300) {
161
+ throw new Error(`CAR-E-MCP: notifications/initialized HTTP ${res.status}(阶段:initialized)`);
162
+ }
163
+ }
164
+ /**
165
+ * 1.9-D32:tools/call 请求的 `traceparent` 头(W3C Trace Context HTTP 载体惯例)——
166
+ * parseTraceparent 校验 fail-open(畸形/空 = {} 不注入不抛;严格 hex 格式排除 CR/LF 头注入面);
167
+ * 与 body params._meta.traceparent 双载体同值并存(1.8-D29 `_meta` 面维持,头为主消费面)。
168
+ */
169
+ function callHeaders(tp) {
170
+ if (!tp || !parseTraceparent(tp))
171
+ return {};
172
+ return { traceparent: tp };
173
+ }
174
+ const transport = {
175
+ send(req, opts) {
176
+ return (async () => {
177
+ if (closed)
178
+ throw new Error('CAR-E-MCP: http transport closed(server 已收口)');
179
+ if (!initialized) {
180
+ initPromise ??= doInitialize().then(() => { initialized = true; });
181
+ try {
182
+ await initPromise;
183
+ }
184
+ catch (e) {
185
+ initPromise = null;
186
+ throw e;
187
+ }
188
+ }
189
+ return postAndRead(req, req.id, 'request', callHeaders(opts?.traceparent));
190
+ })();
191
+ },
192
+ alive: () => !closed,
193
+ close: () => { closed = true; },
194
+ };
195
+ return transport;
196
+ }
@@ -0,0 +1,8 @@
1
+ import type { ClientTransport } from './gateway.ts';
2
+ export interface StdioServerSpec {
3
+ command: string;
4
+ args?: string[];
5
+ }
6
+ export declare function createStdioClientTransport(spec: StdioServerSpec): ClientTransport & {
7
+ readonly alive: () => boolean;
8
+ };
@@ -0,0 +1,73 @@
1
+ /**
2
+ * 1.4-S7 · MCP stdio 客户端传输(W1 MCP 工具进 turn):spawn 子进程 + JSON-RPC 换行分隔 JSON
3
+ *
4
+ * 口径(1.4 规划 W1 MCP 块 / D-18):
5
+ * - CAR → 外部 MCP Server 方向(gateway.ts ClientTransport 的真实通道实装——此前仅测试替身);
6
+ * server 侧对偶(宿主 → CAR)见 src/host/stdio.ts;
7
+ * - 行协议:请求一行 JSON 写入 server stdin;响应按 id 匹配 pending 表(行解析容错跳过非 JSON 行);
8
+ * - send 内建 30s 超时(fail-visible——gateway.register 的 tools/list 无外层 race,防挂死);
9
+ * - stderr 不透传(server 诊断输出与协议通道隔离);close = kill(car run 收口调用)。
10
+ */
11
+ import { spawn } from 'node:child_process';
12
+ export function createStdioClientTransport(spec) {
13
+ const child = spawn(spec.command, spec.args ?? [], { stdio: ['pipe', 'pipe', 'pipe'] });
14
+ const pending = new Map();
15
+ let buffer = '';
16
+ let exited = false;
17
+ child.on('exit', () => { exited = true; });
18
+ child.stdout.on('data', chunk => {
19
+ buffer += chunk.toString('utf-8');
20
+ let nl;
21
+ while ((nl = buffer.indexOf('\n')) >= 0) {
22
+ const line = buffer.slice(0, nl).trim();
23
+ buffer = buffer.slice(nl + 1);
24
+ if (!line)
25
+ continue;
26
+ try {
27
+ const res = JSON.parse(line);
28
+ const settle = pending.get(res.id);
29
+ if (settle) {
30
+ pending.delete(res.id);
31
+ settle(res);
32
+ }
33
+ }
34
+ catch { /* 非 JSON 行容错跳过(server 诊断污染 stdin 协议通道时不断链) */ }
35
+ }
36
+ });
37
+ child.stderr.on('data', () => { });
38
+ child.on('error', () => { exited = true; });
39
+ const transport = {
40
+ send(req) {
41
+ return new Promise((resolve, reject) => {
42
+ if (!transport.alive()) {
43
+ reject(new Error('CAR-E-MCP: stdio transport not alive(server 进程已退出)'));
44
+ return;
45
+ }
46
+ const timer = setTimeout(() => {
47
+ pending.delete(req.id);
48
+ reject(new Error('CAR-E-MCP: stdio send timeout (30s)'));
49
+ }, 30_000);
50
+ pending.set(req.id, res => {
51
+ clearTimeout(timer);
52
+ resolve(res);
53
+ });
54
+ try {
55
+ child.stdin.write(JSON.stringify(req) + '\n');
56
+ }
57
+ catch (e) {
58
+ clearTimeout(timer);
59
+ pending.delete(req.id);
60
+ reject(e);
61
+ }
62
+ });
63
+ },
64
+ alive: () => !exited && child.exitCode === null && !child.killed,
65
+ close: () => {
66
+ try {
67
+ child.kill();
68
+ }
69
+ catch { /* 已退出 */ }
70
+ },
71
+ };
72
+ return transport;
73
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * T-2 · PTC 预算控制(M3-S14)
3
+ *
4
+ * 口径(M3系统设计增补 T-2 / D5 §12 一手口径):
5
+ * - 初始对齐 dsh:computeMs=60s / maxWallMs=600s / maxOutputBytes=64MB;
6
+ * - **三层收敛只许下调**(fail-closed):任何上调 = 显式报错——预算放宽属安全相关变更,
7
+ * 须经标定流程(四步标定法:P99×2 余量校验 + 逃逸验证 + 季度复标)而非配置直改;
8
+ * - 预算定位 = **资源护栏而非安全边界**(containment 非安全边界,对外声明口径归安全设计)。
9
+ */
10
+ export interface PtcBudget {
11
+ computeMs: number;
12
+ maxWallMs: number;
13
+ maxOutputBytes: number;
14
+ }
15
+ /** 初始基线(dsh 一手口径对齐值;标定后经 ADR 修订) */
16
+ export declare const PTC_BUDGET_BASELINE: Readonly<PtcBudget>;
17
+ /**
18
+ * 预算收敛校验:用户覆盖值只许 ≤ 基线(逐项);上调 = CAR-E-BUDGET 显式报错。
19
+ * 返回收敛后的完整预算(未覆盖项取基线)。
20
+ */
21
+ export declare function convergeBudget(override?: Partial<PtcBudget>): PtcBudget;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * T-2 · PTC 预算控制(M3-S14)
3
+ *
4
+ * 口径(M3系统设计增补 T-2 / D5 §12 一手口径):
5
+ * - 初始对齐 dsh:computeMs=60s / maxWallMs=600s / maxOutputBytes=64MB;
6
+ * - **三层收敛只许下调**(fail-closed):任何上调 = 显式报错——预算放宽属安全相关变更,
7
+ * 须经标定流程(四步标定法:P99×2 余量校验 + 逃逸验证 + 季度复标)而非配置直改;
8
+ * - 预算定位 = **资源护栏而非安全边界**(containment 非安全边界,对外声明口径归安全设计)。
9
+ */
10
+ /** 初始基线(dsh 一手口径对齐值;标定后经 ADR 修订) */
11
+ export const PTC_BUDGET_BASELINE = Object.freeze({
12
+ computeMs: 60_000,
13
+ maxWallMs: 600_000,
14
+ maxOutputBytes: 64 * 1024 * 1024,
15
+ });
16
+ /**
17
+ * 预算收敛校验:用户覆盖值只许 ≤ 基线(逐项);上调 = CAR-E-BUDGET 显式报错。
18
+ * 返回收敛后的完整预算(未覆盖项取基线)。
19
+ */
20
+ export function convergeBudget(override) {
21
+ const out = { ...PTC_BUDGET_BASELINE };
22
+ if (!override)
23
+ return out;
24
+ for (const key of ['computeMs', 'maxWallMs', 'maxOutputBytes']) {
25
+ const v = override[key];
26
+ if (v === undefined)
27
+ continue;
28
+ if (!Number.isFinite(v) || v <= 0)
29
+ throw new Error(`CAR-E-BUDGET: ${key} must be a positive number, got ${v}`);
30
+ if (v > PTC_BUDGET_BASELINE[key]) {
31
+ throw new Error(`CAR-E-BUDGET: ${key}=${v} exceeds baseline ${PTC_BUDGET_BASELINE[key]}——预算只许下调(fail-closed),上调须经四步标定流程修订基线`);
32
+ }
33
+ out[key] = v;
34
+ }
35
+ return out;
36
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * T-2 · erasable-only TS 静态检查(M3-S14 双挂点:loader 提交链 + run_code 入口)
3
+ *
4
+ * 口径(M3系统设计增补 T-2 / dsh 源码一手口径 code-runtime-worker-thread/src/index.ts:306-308):
5
+ * - PTC 程序体与插件提交链只接受 erasable TS(Node type-stripping 可直接剥离的语法)——
6
+ * 禁止 enum / namespace / module / 构造器参数属性 / import=require 等需转换的语法;
7
+ * - 双挂点保证「无 worker 即无执行,漏检面 = 0」:loader 在插件装载时检查源码,
8
+ * run_code 在 worker 启动前检查程序体——两处调用同一函数(单一事实源)。
9
+ */
10
+ /** erasable-only 检查:违规 = 显式报错(冲突链定位到语法名;v1 为启发式,行号随 AST 升级) */
11
+ export declare function checkErasableOnly(code: string): {
12
+ ok: boolean;
13
+ violation?: string;
14
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * T-2 · erasable-only TS 静态检查(M3-S14 双挂点:loader 提交链 + run_code 入口)
3
+ *
4
+ * 口径(M3系统设计增补 T-2 / dsh 源码一手口径 code-runtime-worker-thread/src/index.ts:306-308):
5
+ * - PTC 程序体与插件提交链只接受 erasable TS(Node type-stripping 可直接剥离的语法)——
6
+ * 禁止 enum / namespace / module / 构造器参数属性 / import=require 等需转换的语法;
7
+ * - 双挂点保证「无 worker 即无执行,漏检面 = 0」:loader 在插件装载时检查源码,
8
+ * run_code 在 worker 启动前检查程序体——两处调用同一函数(单一事实源)。
9
+ */
10
+ /** erasable 语法黑名单(启发式正则;完整 AST 检查随 jiti 实装升级为语法级) */
11
+ const NON_ERASABLE = [
12
+ { name: 'enum 声明', re: /(?:^|\n)\s*(?:export\s+)?(?:const\s+)?enum\s+[A-Za-z_$]/ },
13
+ { name: 'namespace 声明', re: /(?:^|\n)\s*(?:export\s+)?namespace\s+[A-Za-z_$]/ },
14
+ { name: 'module 声明', re: /(?:^|\n)\s*(?:export\s+)?module\s+[A-Za-z_$"'`]/ },
15
+ { name: '构造器参数属性', re: /constructor\s*\(\s*(?:public|private|protected|readonly)\s+[A-Za-z_$]/ },
16
+ { name: 'import=require', re: /import\s+[A-Za-z_$][\w$]*\s*=\s*require\s*\(/ },
17
+ { name: 'export=require', re: /export\s*=\s*/ },
18
+ { name: '装饰器(实验性,非 erasable)', re: /@\s*[A-Za-z_$][\w$]*\s*\(/ },
19
+ ];
20
+ /** erasable-only 检查:违规 = 显式报错(冲突链定位到语法名;v1 为启发式,行号随 AST 升级) */
21
+ export function checkErasableOnly(code) {
22
+ for (const { name, re } of NON_ERASABLE) {
23
+ if (re.test(code))
24
+ return { ok: false, violation: `non-erasable TS: ${name}(PTC 程序体与插件提交链仅接受 erasable TS,见 M3系统设计增补 T-2)` };
25
+ }
26
+ return { ok: true };
27
+ }
@@ -0,0 +1,65 @@
1
+ import { type PtcBudget } from './budget.ts';
2
+ export interface PtcRequest {
3
+ code: string;
4
+ description: string;
5
+ toolCallId: string;
6
+ budget?: Partial<PtcBudget>;
7
+ }
8
+ export interface PtcResult {
9
+ ok: boolean;
10
+ result?: unknown;
11
+ error?: string;
12
+ wallMs: number;
13
+ outputBytes: number;
14
+ budgetExceeded?: boolean;
15
+ /** F12 出站覆盖命中数(secrets 脱敏在回填上下文前强制执行) */ secretsRedacted?: number;
16
+ }
17
+ export interface ToolBridge {
18
+ run(args: unknown): Promise<unknown>;
19
+ }
20
+ /** 1.8-D31:PTC 桥挂链面(结构兼容 trace.ts PtcRelayHandle——ptc 模块不依赖 runtime-core/trace) */
21
+ export interface PtcSpanBridge {
22
+ complete(startMs: number, endMs: number, outcome: string, error?: unknown): void;
23
+ }
24
+ /** PTC 程序执行:每次新 worker;子调用经消息桥回主线程执行工具(宿主权限门生效面) */
25
+ export interface RunCodeOpts {
26
+ tools: Map<string, ToolBridge>;
27
+ audit?: (d: Record<string, unknown>) => void;
28
+ /** 授权门(S15):返回 false = 拒绝(无 worker 启动,审计留痕);authorizationId 幂等由 authz 服务承载 */ authorize?: (req: PtcRequest) => Promise<boolean>;
29
+ /** 1.8-D31:桥挂链供给(每次 runCode 一次——每程序新 worker;null = 无遥测零 relay) */ spanBridge?: () => {
30
+ complete(startMs: number, endMs: number, outcome: string, error?: unknown): void;
31
+ } | null;
32
+ }
33
+ export declare function runCode(req: PtcRequest, opts: RunCodeOpts): Promise<PtcResult>;
34
+ /** PTC 作为一等 ToolDefinition:强制 write 侧效应(授权门无旁路),接入 runTurn 工具面 */
35
+ export declare function makePtcToolDefinition(opts: {
36
+ tools: Map<string, ToolBridge>;
37
+ audit?: (d: Record<string, unknown>) => void;
38
+ /** 1.8-D31:桥挂链供给透传(每次 runCode 一次;缺省 = 无遥测零 relay) */ spanBridge?: () => {
39
+ complete(startMs: number, endMs: number, outcome: string, error?: unknown): void;
40
+ } | null;
41
+ }): {
42
+ name: string;
43
+ description: string;
44
+ parameters: {
45
+ type: string;
46
+ properties: {
47
+ code: {
48
+ type: string;
49
+ description: string;
50
+ };
51
+ description: {
52
+ type: string;
53
+ description: string;
54
+ };
55
+ };
56
+ required: string[];
57
+ additionalProperties: boolean;
58
+ };
59
+ declaredSideEffect: 'write';
60
+ run: (args: {
61
+ code: string;
62
+ description: string;
63
+ toolCallId?: string;
64
+ }) => Promise<unknown>;
65
+ };
@@ -0,0 +1,140 @@
1
+ /**
2
+ * F16 · PTC(Programmatic Tool Calling)执行器(M3-S14)
3
+ *
4
+ * 口径(M3系统设计增补 T-2 / 决议③ PTC 落位 / dsh 一手口径):
5
+ * - run_code 签名:code + description **双必填**(description = 授权门「凭什么」人审凭据,无默认值);
6
+ * - 隔离:每次新 worker(独立无跨运行状态);containment 非安全边界 → **强制 declaredSideEffect='write'**
7
+ * 走 stop.ts 既有权限门(S15 authorizationId='ptc-'+id 幂等集成);
8
+ * - erasable-only 双挂点之一(本入口);挂点二在 loader 提交链(mountPlugin);
9
+ * - **预算到期不掐 turn**:超限 = 工具错误结果交回模型(TurnEndReason 六值零扩展,ADR-001 兼容);
10
+ * - 并发护栏(自研五维):并发 4 / 子调用超时 30s / 总 worker 存活由本执行器串行门管控。
11
+ *
12
+ * 1.8(D-31 PTC worker 桥挂链):opts.spanBridge 加法(otel 显式开且 turn span 存活时非空)——
13
+ * worker 侧程序计时(Date.now,worker_threads 同进程同时钟域)经 done 消息加法 span 字段回传,
14
+ * 主线程经 bridge.complete() 单点收口 car.ptc span(trace.ts ptcRelay——trace 挂链 turn /
15
+ * facade 时刻覆盖);**单 exporter 纪律:worker 零独立出站通道、零 trace 载荷注入**(worker 无
16
+ * outbound 面——挂链由主线程句柄归因 + 计时 relay 完成)。主侧 killer / worker error 兜底路径
17
+ * 无 done 回传(terminate/崩溃抢先),以主线程时钟收口(口径登记:兜底非 worker 计时)。
18
+ */
19
+ import { Worker } from 'node:worker_threads';
20
+ import { fileURLToPath } from 'node:url';
21
+ import { dirname, join } from 'node:path';
22
+ import { convergeBudget } from './budget.js';
23
+ import { checkErasableOnly } from './erasable.js';
24
+ import { redactSecrets } from '../security/secrets.js';
25
+ const WORKER_PATH = join(dirname(fileURLToPath(import.meta.url)), 'worker-entry.ts');
26
+ const MAX_CONCURRENT = 4;
27
+ let active = 0;
28
+ export async function runCode(req, opts) {
29
+ // 双必填(description 为授权凭据——S15 授权门消费)
30
+ if (!req.code?.trim())
31
+ throw new Error('CAR-E-PTC: code is required(run_code 双必填)');
32
+ if (!req.description?.trim())
33
+ throw new Error('CAR-E-PTC: description is required——授权门人审凭据(无默认值,M3系统设计增补 T-2)');
34
+ // 授权门前置(无 worker 启动):拒绝 = 工具错误结果 + 审计留痕(authorizationId='ptc-'+id 幂等键)
35
+ if (opts.authorize) {
36
+ const granted = await opts.authorize(req);
37
+ if (!granted) {
38
+ opts.audit?.({ kind: 'ptc-denied', toolCallId: req.toolCallId, authorizationId: 'ptc-' + req.toolCallId, description: req.description });
39
+ return { ok: false, error: 'authorization-denied (ptc)——授权拒绝落审计,程序未执行', wallMs: 0, outputBytes: 0 };
40
+ }
41
+ }
42
+ // erasable-only 挂点(入口)
43
+ const era = checkErasableOnly(req.code);
44
+ if (!era.ok)
45
+ throw new Error(`CAR-E-PTC: ${era.violation}`);
46
+ const budget = convergeBudget(req.budget);
47
+ if (active >= MAX_CONCURRENT) {
48
+ return { ok: false, error: 'CAR-E-PTC: concurrency guard (max=4)——超限拒绝留痕', wallMs: 0, outputBytes: 0 };
49
+ }
50
+ const started = Date.now();
51
+ active++;
52
+ // 1.8-D31:桥挂链供给(每次 runCode 一次);null = 无遥测零 relay
53
+ const relay = opts.spanBridge?.() ?? null;
54
+ opts.audit?.({ kind: 'ptc-start', description: req.description, toolCallId: req.toolCallId, budget });
55
+ try {
56
+ return await new Promise((resolve) => {
57
+ const worker = new Worker(WORKER_PATH, {
58
+ workerData: { code: req.code, budget },
59
+ resourceLimits: { maxOldGenerationSizeMb: 256, maxYoungGenerationSizeMb: 32 },
60
+ });
61
+ // 主线程侧兜底(worker 内计时器失效时强杀——双层预算)
62
+ const killer = setTimeout(() => {
63
+ void worker.terminate();
64
+ // 1.8-D31:兜底收口(主线程时钟——terminate 抢先无 done relay,口径登记)
65
+ relay?.complete(started, Date.now(), 'error', new Error(`budget-exceeded (maxWallMs=${budget.maxWallMs}, main-side guard)`));
66
+ resolve({ ok: false, error: `budget-exceeded (maxWallMs=${budget.maxWallMs}, main-side guard)`, wallMs: Date.now() - started, outputBytes: 0, budgetExceeded: true });
67
+ }, budget.maxWallMs + 1000);
68
+ worker.on('message', (m) => {
69
+ if (m.type === 'tool') {
70
+ const def = opts.tools.get(m.name);
71
+ // 工具异常必须回传为 tool-result error(供程序体 try/catch 恢复)——
72
+ // 无 catch 会成为 unhandled rejection 崩溃主进程(S19 评测 E1 捕获)
73
+ void (def
74
+ ? def.run(m.args).then(r => worker.postMessage({ type: 'tool-result', callId: m.callId, result: r }), (e) => worker.postMessage({ type: 'tool-result', callId: m.callId, error: String(e) }))
75
+ : Promise.resolve(worker.postMessage({ type: 'tool-result', callId: m.callId, error: `unknown tool "${m.name}"` })));
76
+ return;
77
+ }
78
+ if (m.type === 'done') {
79
+ clearTimeout(killer);
80
+ // 1.8-D31:worker 计时 relay 收口(error 路径同样收口——D-26 exception 形态成立)
81
+ if (m.span && relay)
82
+ relay.complete(m.span.startMs, m.span.endMs, m.error ? 'error' : 'ok', m.error);
83
+ // F12 出站覆盖:worker 输出回填上下文前强制 redact(M3安全设计增补 T-4)
84
+ let result = m.result;
85
+ let secretsRedacted = 0;
86
+ if (result !== undefined) {
87
+ const serialized = JSON.stringify(result);
88
+ const r = redactSecrets(serialized);
89
+ secretsRedacted = r.redacted;
90
+ if (r.redacted > 0) {
91
+ result = JSON.parse(r.text);
92
+ opts.audit?.({ kind: 'ptc-secrets-redacted', toolCallId: req.toolCallId, hits: r.redacted });
93
+ }
94
+ }
95
+ resolve({
96
+ ok: !m.error,
97
+ result,
98
+ error: m.error,
99
+ wallMs: Date.now() - started,
100
+ outputBytes: Buffer.byteLength(JSON.stringify(result ?? null)),
101
+ budgetExceeded: !!m.error?.startsWith('budget-exceeded'),
102
+ secretsRedacted,
103
+ });
104
+ }
105
+ });
106
+ worker.on('error', (e) => { clearTimeout(killer); relay?.complete(started, Date.now(), 'error', e); resolve({ ok: false, error: String(e), wallMs: Date.now() - started, outputBytes: 0 }); });
107
+ worker.on('exit', (code) => { clearTimeout(killer); active--; });
108
+ // exit 可能先于 done(budget killer)——兜底 resolve 幂等由 Promise 语义保证
109
+ });
110
+ }
111
+ finally {
112
+ // active 递归在 exit 事件处理;此处为异常路径兜底
113
+ setTimeout(() => { active = Math.max(0, active - 0); }, 0);
114
+ }
115
+ }
116
+ /** PTC 作为一等 ToolDefinition:强制 write 侧效应(授权门无旁路),接入 runTurn 工具面 */
117
+ export function makePtcToolDefinition(opts) {
118
+ return {
119
+ name: 'run_code',
120
+ description: '执行一段 erasable TypeScript 程序(PTC)——程序体内经 tools.<name>(args) 调用已注册工具',
121
+ // 1.4-S7:run_code 入参 schema(进模型声明面——此前无 parameters,模型看不到 code/description 形状)
122
+ parameters: {
123
+ type: 'object',
124
+ properties: {
125
+ code: { type: 'string', description: 'erasable TS 程序体(async 函数体;经 tools.<name>(args) 调用已注册工具)' },
126
+ description: { type: 'string', description: '本段程序的一句话描述(审计留痕)' },
127
+ },
128
+ required: ['code', 'description'],
129
+ additionalProperties: false,
130
+ },
131
+ // T-22 语义:PTC 与 bash 同信任级别 → 强制 write 最高约束(不可声明为 readonly)
132
+ declaredSideEffect: 'write',
133
+ run: async (args) => {
134
+ const r = await runCode({ code: args.code, description: args.description, toolCallId: args.toolCallId ?? 'ptc-' + Date.now().toString(36) }, opts);
135
+ if (!r.ok)
136
+ throw new Error(r.error);
137
+ return r.result;
138
+ },
139
+ };
140
+ }
@@ -0,0 +1,15 @@
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
+ /**
12
+ * JSON Schema 节点 → TS 类型表达式(深度递归)。
13
+ * 任何不可判定路径返回带内联原因标记的 unknown 字符串(形如 unknown + 注释)——可读、可测、不静默。
14
+ */
15
+ export declare function jsonSchemaToTs(schema: unknown, depth?: number): string;