@tencent-ai/agent-sdk 0.3.229 → 0.3.230-dev.80d16dd.202607272156
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/CHANGELOG.md +10 -0
- package/cli/dist/codebuddy-headless.js +309 -291
- package/cli/package.json +3 -2
- package/cli/product.cloudhosted.json +2 -2
- package/cli/product.internal.json +2 -2
- package/cli/product.ioa.json +2 -2
- package/cli/product.json +2 -2
- package/cli/product.selfhosted.json +2 -2
- package/lib/_internal/query-controller.d.ts +48 -2
- package/lib/_internal/query-controller.d.ts.map +1 -1
- package/lib/_internal/query-controller.js +335 -2
- package/lib/_internal/query-controller.js.map +1 -1
- package/lib/_internal/remote-extensions-options.d.ts +3 -0
- package/lib/_internal/remote-extensions-options.d.ts.map +1 -0
- package/lib/_internal/remote-extensions-options.js +37 -0
- package/lib/_internal/remote-extensions-options.js.map +1 -0
- package/lib/_internal/session-mirror.d.ts +53 -0
- package/lib/_internal/session-mirror.d.ts.map +1 -0
- package/lib/_internal/session-mirror.js +156 -0
- package/lib/_internal/session-mirror.js.map +1 -0
- package/lib/_internal/session-store-runtime.d.ts +52 -0
- package/lib/_internal/session-store-runtime.d.ts.map +1 -0
- package/lib/_internal/session-store-runtime.js +283 -0
- package/lib/_internal/session-store-runtime.js.map +1 -0
- package/lib/in-memory-session-store.d.ts +31 -0
- package/lib/in-memory-session-store.d.ts.map +1 -0
- package/lib/in-memory-session-store.js +127 -0
- package/lib/in-memory-session-store.js.map +1 -0
- package/lib/index.d.ts +13 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +16 -1
- package/lib/index.js.map +1 -1
- package/lib/mcp/delegated-redaction.d.ts +47 -0
- package/lib/mcp/delegated-redaction.d.ts.map +1 -0
- package/lib/mcp/delegated-redaction.js +362 -0
- package/lib/mcp/delegated-redaction.js.map +1 -0
- package/lib/mcp/index.d.ts +4 -0
- package/lib/mcp/index.d.ts.map +1 -1
- package/lib/mcp/index.js +8 -1
- package/lib/mcp/index.js.map +1 -1
- package/lib/mcp/managed-remote-mcp-bridge.d.ts +49 -0
- package/lib/mcp/managed-remote-mcp-bridge.d.ts.map +1 -0
- package/lib/mcp/managed-remote-mcp-bridge.js +303 -0
- package/lib/mcp/managed-remote-mcp-bridge.js.map +1 -0
- package/lib/query.d.ts +14 -1
- package/lib/query.d.ts.map +1 -1
- package/lib/query.js +320 -28
- package/lib/query.js.map +1 -1
- package/lib/remote-error-codes.d.ts +8 -0
- package/lib/remote-error-codes.d.ts.map +1 -0
- package/lib/remote-error-codes.js +23 -0
- package/lib/remote-error-codes.js.map +1 -0
- package/lib/session-store-protocol.d.ts +19 -0
- package/lib/session-store-protocol.d.ts.map +1 -0
- package/lib/session-store-protocol.js +125 -0
- package/lib/session-store-protocol.js.map +1 -0
- package/lib/session.d.ts +8 -1
- package/lib/session.d.ts.map +1 -1
- package/lib/session.js +132 -11
- package/lib/session.js.map +1 -1
- package/lib/transport/index.d.ts +53 -3
- package/lib/transport/index.d.ts.map +1 -1
- package/lib/transport/index.js.map +1 -1
- package/lib/transport/process-transport.d.ts +81 -4
- package/lib/transport/process-transport.d.ts.map +1 -1
- package/lib/transport/process-transport.js +295 -15
- package/lib/transport/process-transport.js.map +1 -1
- package/lib/types.d.ts +629 -6
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +14 -1
- package/lib/types.js.map +1 -1
- package/package.json +3 -2
|
@@ -3,8 +3,32 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Transport implementation that spawns CLI process and communicates via stdin/stdout.
|
|
5
5
|
*/
|
|
6
|
+
import { type DelegatedServerDescriptorHost, type RealMcpTransport } from '../mcp/managed-remote-mcp-bridge';
|
|
6
7
|
import type { ControlRequest, ControlRequestPayload, Message, SubscriptionChannel, UserMessage } from '../types';
|
|
7
8
|
import type { NotificationHandler, ProcessTransportOptions, Transport } from './index';
|
|
9
|
+
/**
|
|
10
|
+
* 委托 server 注册描述(宿主声明,不含 url / token)。
|
|
11
|
+
* serverName 只在当前 control connection / session scope 内唯一,真实 endpoint/token 由
|
|
12
|
+
* 受管层在 createTransport 内解析,不经过 CLI。
|
|
13
|
+
*/
|
|
14
|
+
export interface DelegatedServerRegistration {
|
|
15
|
+
serverName: string;
|
|
16
|
+
transport: 'http' | 'sse' | 'remote';
|
|
17
|
+
capabilityId?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 受管层提供的委托 transport 工厂:按 scopedKey + descriptor 解析真实 endpoint/token 并建 transport。
|
|
21
|
+
* 部署方注入,测试用 mock 替代;这里不实现真实 endpoint / OAuth 解析。
|
|
22
|
+
*/
|
|
23
|
+
export type CreateDelegatedMcpTransport = (scopedKey: string, descriptor: DelegatedServerDescriptorHost) => Promise<RealMcpTransport>;
|
|
24
|
+
/**
|
|
25
|
+
* transport 关闭的终止原因分类,用于决定是否需要合成一条补偿 error 帧。
|
|
26
|
+
*
|
|
27
|
+
* 只有异常类原因(进程非零退出、退出前未输出 result、stdout 意外关闭、spawn 失败)
|
|
28
|
+
* 才会触发合成;已经观察到 result / error 帧或上层主动关闭的场景下不合成,
|
|
29
|
+
* 避免将正常结束误报为失败。
|
|
30
|
+
*/
|
|
31
|
+
export type TerminalCause = 'sawResult' | 'sawError' | 'explicitClose' | 'processExitNonZero' | 'processExitZeroBeforeResult' | 'stdoutClosedUnexpectedly' | 'spawnError';
|
|
8
32
|
/**
|
|
9
33
|
* ProcessTransport - communicates with CLI via spawned process.
|
|
10
34
|
*/
|
|
@@ -21,12 +45,19 @@ export declare class ProcessTransport implements Transport {
|
|
|
21
45
|
private _stderrBuffer;
|
|
22
46
|
private readyPromise;
|
|
23
47
|
private _isCliReady;
|
|
48
|
+
private _sessionId?;
|
|
49
|
+
private _currentRunId?;
|
|
50
|
+
private _sawRunStarted;
|
|
51
|
+
private _sawResult;
|
|
52
|
+
private _sawError;
|
|
24
53
|
private _writeQueue;
|
|
25
54
|
private _draining;
|
|
26
55
|
private sdkMcpTransports;
|
|
27
56
|
private sdkMcpServerInstances;
|
|
28
57
|
private pendingMcpResponses;
|
|
29
58
|
private _sdkMcpServerNames;
|
|
59
|
+
private managedRemoteMcpBridge?;
|
|
60
|
+
private registeredDelegatedScopedKeys;
|
|
30
61
|
private _cliVersion;
|
|
31
62
|
private notificationHandlers;
|
|
32
63
|
constructor(options?: ProcessTransportOptions);
|
|
@@ -109,7 +140,7 @@ export declare class ProcessTransport implements Transport {
|
|
|
109
140
|
/**
|
|
110
141
|
* Send a user message to CLI.
|
|
111
142
|
*/
|
|
112
|
-
sendUserMessage(content: string | UserMessage, traceId?: string, parentSpanId?: string): void;
|
|
143
|
+
sendUserMessage(content: string | UserMessage, traceId?: string, parentSpanId?: string, remoteContext?: import('../types').RemoteOperationContext): void;
|
|
113
144
|
/**
|
|
114
145
|
* Send a control request and wait for response.
|
|
115
146
|
*/
|
|
@@ -126,7 +157,7 @@ export declare class ProcessTransport implements Transport {
|
|
|
126
157
|
/**
|
|
127
158
|
* Send a control error response.
|
|
128
159
|
*/
|
|
129
|
-
sendControlErrorResponse(requestId: string, error: string): void;
|
|
160
|
+
sendControlErrorResponse(requestId: string, error: string, code?: string): void;
|
|
130
161
|
/**
|
|
131
162
|
* Check if transport is ready.
|
|
132
163
|
* Returns true only when CLI has produced its first output.
|
|
@@ -136,14 +167,31 @@ export declare class ProcessTransport implements Transport {
|
|
|
136
167
|
* Register a handler for control notifications from a specific channel.
|
|
137
168
|
*/
|
|
138
169
|
onNotification(channel: SubscriptionChannel, handler: NotificationHandler): void;
|
|
170
|
+
/**
|
|
171
|
+
* 向消息流注入一条 SDK 侧合成的消息(如 mirror_error 帧)。
|
|
172
|
+
* 流未建立或已结束时静默丢弃——此时没有消费者,合成帧无处投递。
|
|
173
|
+
*/
|
|
174
|
+
enqueueMessage(message: Message): void;
|
|
139
175
|
/**
|
|
140
176
|
* Remove a notification handler for a specific channel.
|
|
141
177
|
*/
|
|
142
178
|
offNotification(channel: SubscriptionChannel, handler: NotificationHandler): void;
|
|
143
179
|
/**
|
|
144
|
-
*
|
|
180
|
+
* 关闭 transport。
|
|
181
|
+
*
|
|
182
|
+
* cause 标明终止原因:仅在异常类原因(进程非零退出、退出前未输出 result、
|
|
183
|
+
* stdout 意外关闭、spawn 失败)下才合成一条 error 帧;正常结束与主动关闭均不合成,
|
|
184
|
+
* 避免将成功的 run 误报为失败。
|
|
185
|
+
*
|
|
186
|
+
* 已有调用点(例如上层 Session.close())不传 cause 时按主动关闭处理,
|
|
187
|
+
* 与"手动关闭不产生虚假 error"的既有行为保持一致。
|
|
145
188
|
*/
|
|
146
|
-
close(): void;
|
|
189
|
+
close(cause?: TerminalCause): void;
|
|
190
|
+
/**
|
|
191
|
+
* 为合成帧生成 uuid。旧 Node 运行时可能未内置 WebCrypto,
|
|
192
|
+
* crypto.randomUUID 不可用时降级为"时间戳 + 随机串"的形式。
|
|
193
|
+
*/
|
|
194
|
+
private randomUuid;
|
|
147
195
|
private handleLine;
|
|
148
196
|
private handleControlNotification;
|
|
149
197
|
private handleControlResponse;
|
|
@@ -171,6 +219,35 @@ export declare class ProcessTransport implements Transport {
|
|
|
171
219
|
* Handle MCP message control request from CLI.
|
|
172
220
|
*/
|
|
173
221
|
private handleMcpMessageRequest;
|
|
222
|
+
/**
|
|
223
|
+
* 注册远程委托 server(宿主驱动)。
|
|
224
|
+
*
|
|
225
|
+
* 首次调用时懒构造 ManagedRemoteMcpBridge:createTransport 由受管层注入(解析真实
|
|
226
|
+
* endpoint/token),redactResponse 接线为 redactDelegatedResponse(响应离开宿主回到 CLI
|
|
227
|
+
* 之前先脱敏)。随后对每个 descriptor 用与路由一致的 resolveDelegatedScopedKey 生成 scoped
|
|
228
|
+
* key 并注册,key 记入集合供 close 统一注销。
|
|
229
|
+
*
|
|
230
|
+
* 接线现状:ProcessTransport 构造期在 `delegatedMcpServers` + `mcpTransportFactory` 齐备时
|
|
231
|
+
* 自动调用本方法(host-driven v1 必做)。`unregisterAllDelegatedServers` 已接入
|
|
232
|
+
* close 兜底,control connection 关闭时统一释放 transport 与 pending。
|
|
233
|
+
*/
|
|
234
|
+
registerDelegatedServers(descriptors: DelegatedServerRegistration[], createTransport: CreateDelegatedMcpTransport): void;
|
|
235
|
+
/**
|
|
236
|
+
* 注销所有已注册的委托 server(reject 在途 pending、关闭真实 transport)。
|
|
237
|
+
* 由 close 调用;单个注销失败不阻塞其他。
|
|
238
|
+
*/
|
|
239
|
+
private unregisterAllDelegatedServers;
|
|
240
|
+
/**
|
|
241
|
+
* 组合远程委托 server 的 scoped key。
|
|
242
|
+
*
|
|
243
|
+
* 完整 scoped key 语义是 control connection / tenant / user / session / run + serverName。
|
|
244
|
+
* 本 transport 从 ProcessTransportOptions 拿到 controlConnectionId /
|
|
245
|
+
* tenantId / userId / sessionId / runId,按字符集校验后用分隔符 `::` 拼接,缺失维度直接跳过
|
|
246
|
+
* (不写空段,避免 `::` 退化成空字符串导致跨 scope 撞 key)。所有维度都缺时才退化为裸
|
|
247
|
+
* serverName——这是最后兜底,部署方应至少注入 controlConnectionId / sessionId 之一。
|
|
248
|
+
* 注册与路由必须复用这同一组合逻辑,两侧的 key 才能一致。
|
|
249
|
+
*/
|
|
250
|
+
private resolveDelegatedScopedKey;
|
|
174
251
|
/**
|
|
175
252
|
* Handle MCP request and wait for response from MCP server.
|
|
176
253
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-transport.d.ts","sourceRoot":"","sources":["../../src/transport/process-transport.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,KAAK,EAGR,cAAc,EACd,qBAAqB,EAIrB,OAAO,EACP,mBAAmB,EACnB,WAAW,EACd,MAAM,UAAU,CAAC;AAGlB,OAAO,KAAK,EAAE,mBAAmB,EAAE,uBAAuB,EAAC,SAAS,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"process-transport.d.ts","sourceRoot":"","sources":["../../src/transport/process-transport.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,EACH,KAAK,6BAA6B,EAElC,KAAK,gBAAgB,EACxB,MAAM,kCAAkC,CAAC;AAG1C,OAAO,KAAK,EAGR,cAAc,EACd,qBAAqB,EAIrB,OAAO,EACP,mBAAmB,EACnB,WAAW,EACd,MAAM,UAAU,CAAC;AAGlB,OAAO,KAAK,EAAE,mBAAmB,EAAE,uBAAuB,EAAC,SAAS,EAAE,MAAM,SAAS,CAAC;AAgBtF;;;;GAIG;AACH,MAAM,WAAW,2BAA2B;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,MAAM,2BAA2B,GAAG,CACtC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,6BAA6B,KACxC,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAY/B;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GACnB,WAAW,GACX,UAAU,GACV,eAAe,GACf,oBAAoB,GACpB,6BAA6B,GAC7B,0BAA0B,GAC1B,YAAY,CAAC;AAqFnB;;GAEG;AACH,qBAAa,gBAAiB,YAAW,SAAS;IAC9C,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,EAAE,CAAmC;IAC7C,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,aAAa,CAAgC;IACrD,OAAO,CAAC,qBAAqB,CAAC,CAAoC;IAClE,OAAO,CAAC,eAAe,CAA6C;IACpE,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,aAAa,CAAM;IAC3B,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,WAAW,CAAS;IAS5B,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,aAAa,CAAC,CAAS;IAC/B,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAS;IAG1B,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,SAAS,CAAS;IAG1B,OAAO,CAAC,gBAAgB,CAAqD;IAC7E,OAAO,CAAC,qBAAqB,CAAqC;IAClE,OAAO,CAAC,mBAAmB,CAA8C;IACzE,OAAO,CAAC,kBAAkB,CAAgB;IAG1C,OAAO,CAAC,sBAAsB,CAAC,CAAyB;IAExD,OAAO,CAAC,6BAA6B,CAA0B;IAG/D,OAAO,CAAC,WAAW,CAAuB;IAG1C,OAAO,CAAC,oBAAoB,CAAiE;IAE7F,YAAY,OAAO,GAAE,uBAA4B,EAoChD;IAED;;;OAGG;IACH,IAAI,iBAAiB,IAAI,MAAM,EAAE,CAEhC;IAED;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAK5B;IAED;;OAEG;IACH,QAAQ,IAAI,MAAM,GAAG,SAAS,CAE7B;IAED;;;OAGG;IACH,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAKpC;IAED;;OAEG;IACH,iBAAiB,IAAI,MAAM,GAAG,SAAS,CAEtC;IAED;;;;;OAKG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CA4BjC;IAED;;OAEG;IACH,OAAO,CAAC,cAAc;IAiBtB;;;;;;OAMG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAE7B;IAED;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAoBxB;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAW5B;;;OAGG;IACH,OAAO,CAAC,SAAS;IAoLjB;;OAEG;IACH,KAAK,IAAI,IAAI,CA2GZ;IAED;;OAEG;IACI,QAAQ,IAAI,cAAc,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAexD;IAED;;;;OAIG;IACH,sBAAsB,IAAI,IAAI,CAE7B;IAED;;;;;OAKG;IACH,gBAAgB,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,GAAG,IAAI,CAEjE;IAED;;OAEG;IACH,eAAe,CACX,OAAO,EAAE,MAAM,GAAG,WAAW,EAC7B,OAAO,CAAC,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,EACrB,aAAa,CAAC,EAAE,OAAO,UAAU,EAAE,sBAAsB,GAC1D,IAAI,CAqCN;IAED;;OAEG;IACG,kBAAkB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChD,OAAO,EAAE,qBAAqB,EAC9B,SAAS,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,CAAC,CAAC,CAgCZ;IAED;;;OAGG;IACH,wBAAwB,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAc7D;IAED;;OAEG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAU9E;IAED;;OAEG;IACH,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAW9E;IAED;;;OAGG;IACH,OAAO,IAAI,OAAO,CAEjB;IAED;;OAEG;IACH,cAAc,CAAC,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAO/E;IAED;;;OAGG;IACH,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAIrC;IAED;;OAEG;IACH,eAAe,CAAC,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAQhF;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,KAAK,GAAE,aAA+B,GAAG,IAAI,CAiFlD;IAED;;;OAGG;IACH,OAAO,CAAC,UAAU;IAclB,OAAO,CAAC,UAAU;IAoFlB,OAAO,CAAC,yBAAyB;IAajC,OAAO,CAAC,qBAAqB;IAoB7B,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,kBAAkB;IAK1B,OAAO,CAAC,iBAAiB;IAazB,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,SAAS;IAqBjB,OAAO,CAAC,gBAAgB;IAwBxB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IA6BzB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAc3B;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAgCjC;;OAEG;YACW,uBAAuB;IAsErC;;;;;;;;;;;OAWG;IACH,wBAAwB,CACpB,WAAW,EAAE,2BAA2B,EAAE,EAC1C,eAAe,EAAE,2BAA2B,GAC7C,IAAI,CAiCN;IAED;;;OAGG;IACH,OAAO,CAAC,6BAA6B;IAYrC;;;;;;;;;OASG;IACH,OAAO,CAAC,yBAAyB;IA4BjC;;OAEG;IACH,OAAO,CAAC,uBAAuB;CAiClC"}
|
|
@@ -41,7 +41,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
41
41
|
exports.ProcessTransport = void 0;
|
|
42
42
|
const node_child_process_1 = require("node:child_process");
|
|
43
43
|
const readline = __importStar(require("node:readline"));
|
|
44
|
+
const delegated_redaction_1 = require("../mcp/delegated-redaction");
|
|
45
|
+
const managed_remote_mcp_bridge_1 = require("../mcp/managed-remote-mcp-bridge");
|
|
44
46
|
const sdk_control_server_transport_1 = require("../mcp/sdk-control-server-transport");
|
|
47
|
+
const remote_error_codes_1 = require("../remote-error-codes");
|
|
45
48
|
const utils_1 = require("../utils");
|
|
46
49
|
const version_1 = require("../version");
|
|
47
50
|
function createDeferred() {
|
|
@@ -53,6 +56,12 @@ function createDeferred() {
|
|
|
53
56
|
});
|
|
54
57
|
return { promise, resolve, reject };
|
|
55
58
|
}
|
|
59
|
+
const ERROR_TERMINAL_CAUSES = new Set([
|
|
60
|
+
'processExitNonZero',
|
|
61
|
+
'processExitZeroBeforeResult',
|
|
62
|
+
'stdoutClosedUnexpectedly',
|
|
63
|
+
'spawnError',
|
|
64
|
+
]);
|
|
56
65
|
/**
|
|
57
66
|
* Resolve the runtime configuration for spawning CLI process.
|
|
58
67
|
*
|
|
@@ -121,6 +130,9 @@ class ProcessTransport {
|
|
|
121
130
|
this._stderrBuffer = '';
|
|
122
131
|
this.readyPromise = null;
|
|
123
132
|
this._isCliReady = false;
|
|
133
|
+
this._sawRunStarted = false;
|
|
134
|
+
this._sawResult = false;
|
|
135
|
+
this._sawError = false;
|
|
124
136
|
// Write queue for backpressure handling
|
|
125
137
|
this._writeQueue = [];
|
|
126
138
|
this._draining = false;
|
|
@@ -129,6 +141,8 @@ class ProcessTransport {
|
|
|
129
141
|
this.sdkMcpServerInstances = new Map();
|
|
130
142
|
this.pendingMcpResponses = new Map();
|
|
131
143
|
this._sdkMcpServerNames = [];
|
|
144
|
+
// 已注册的委托 server scoped key 集合,供 close 时统一注销,避免残留 pending / transport。
|
|
145
|
+
this.registeredDelegatedScopedKeys = new Set();
|
|
132
146
|
// Cached CLI version for UserAgent
|
|
133
147
|
this._cliVersion = null;
|
|
134
148
|
// Notification handlers by channel
|
|
@@ -150,6 +164,17 @@ class ProcessTransport {
|
|
|
150
164
|
this.connectSdkMcpServer(name, server);
|
|
151
165
|
}
|
|
152
166
|
}
|
|
167
|
+
// 接线 host-driven 委托 bridge:descriptor + transport 工厂齐备时,构造期即注册
|
|
168
|
+
// scoped key → descriptor 到 ManagedRemoteMcpBridge。bridge 不会主动建 transport,
|
|
169
|
+
// 真实 transport 在第一次 mcp_message 到达时按需懒建。
|
|
170
|
+
// 缺 mcpTransportFactory 时只跳过注册(CLI 会因 bridge 未命中显式 fail-closed)。
|
|
171
|
+
if (this.options.delegatedMcpServers && this.options.delegatedMcpServers.length > 0 && this.options.mcpTransportFactory) {
|
|
172
|
+
this.registerDelegatedServers(this.options.delegatedMcpServers.map(d => ({
|
|
173
|
+
serverName: d.serverName,
|
|
174
|
+
transport: d.transport,
|
|
175
|
+
capabilityId: d.capabilityId,
|
|
176
|
+
})), this.options.mcpTransportFactory);
|
|
177
|
+
}
|
|
153
178
|
}
|
|
154
179
|
/**
|
|
155
180
|
* Get the list of SDK MCP server names.
|
|
@@ -345,6 +370,9 @@ class ProcessTransport {
|
|
|
345
370
|
if (opts.forkSession) {
|
|
346
371
|
args.push('--fork-session');
|
|
347
372
|
}
|
|
373
|
+
if (opts.sessionStoreMode) {
|
|
374
|
+
args.push('--session-store-mode', opts.sessionStoreMode);
|
|
375
|
+
}
|
|
348
376
|
// MCP options
|
|
349
377
|
if (opts.mcpServers) {
|
|
350
378
|
if (typeof opts.mcpServers === 'string') {
|
|
@@ -358,6 +386,32 @@ class ProcessTransport {
|
|
|
358
386
|
if (opts.strictMcpConfig) {
|
|
359
387
|
args.push('--strict-mcp-config');
|
|
360
388
|
}
|
|
389
|
+
// FileSystem replacement: serialize explicit modules selection (handler stays in-process)
|
|
390
|
+
if (opts.fileSystem) {
|
|
391
|
+
args.push('--file-system-config', JSON.stringify(opts.fileSystem));
|
|
392
|
+
}
|
|
393
|
+
// Channel replacement: pass empty descriptor to opt in; handler stays in-process
|
|
394
|
+
if (opts.channel) {
|
|
395
|
+
args.push('--channel-config', JSON.stringify({}));
|
|
396
|
+
}
|
|
397
|
+
// Exec replacement: serialize maxOutputBytes / capabilityId / remoteExecutionContextId
|
|
398
|
+
// (handler stays in-process). Presence of --exec-config enables remote exec in cbc.
|
|
399
|
+
if (opts.exec) {
|
|
400
|
+
args.push('--exec-config', JSON.stringify({
|
|
401
|
+
...(opts.exec.maxOutputBytes ? { maxOutputBytes: opts.exec.maxOutputBytes } : {}),
|
|
402
|
+
...(opts.exec.capabilityId ? { capabilityId: opts.exec.capabilityId } : {}),
|
|
403
|
+
...(opts.exec.remoteExecutionContextId ? { remoteExecutionContextId: opts.exec.remoteExecutionContextId } : {}),
|
|
404
|
+
}));
|
|
405
|
+
}
|
|
406
|
+
if (opts.remoteExtensions) {
|
|
407
|
+
args.push('--remote-extensions');
|
|
408
|
+
}
|
|
409
|
+
// Local-execution deny list. Only emit when it actually denies something, so
|
|
410
|
+
// that "unset" and "[]" (empty deny list) produce byte-identical argv/logs.
|
|
411
|
+
const led = opts.localExecutionDeny;
|
|
412
|
+
if (led === 'all' || (Array.isArray(led) && led.length > 0)) {
|
|
413
|
+
args.push('--local-execution-deny', JSON.stringify(led));
|
|
414
|
+
}
|
|
361
415
|
// Settings
|
|
362
416
|
// SDK default: don't load any filesystem settings for clean environment isolation
|
|
363
417
|
// When settingSources is explicitly provided (including empty array), use it
|
|
@@ -453,6 +507,9 @@ class ProcessTransport {
|
|
|
453
507
|
if (!env.CODEBUDDY_DISABLE_AUTO_MEMORY) {
|
|
454
508
|
env.CODEBUDDY_DISABLE_AUTO_MEMORY = '1';
|
|
455
509
|
}
|
|
510
|
+
if (this.options.remoteExtensions) {
|
|
511
|
+
env.CODEBUDDY_DISABLE_PRODUCT_CACHE = '1';
|
|
512
|
+
}
|
|
456
513
|
// Thinking configuration → MAX_THINKING_TOKENS env var
|
|
457
514
|
this.applyThinkingEnv(env);
|
|
458
515
|
// Build SDK UserAgent and inject via CODEBUDDY_CUSTOM_HEADERS
|
|
@@ -551,7 +608,7 @@ class ProcessTransport {
|
|
|
551
608
|
/**
|
|
552
609
|
* Send a user message to CLI.
|
|
553
610
|
*/
|
|
554
|
-
sendUserMessage(content, traceId, parentSpanId) {
|
|
611
|
+
sendUserMessage(content, traceId, parentSpanId, remoteContext) {
|
|
555
612
|
// Ensure transport is started
|
|
556
613
|
this.start();
|
|
557
614
|
let message;
|
|
@@ -566,16 +623,21 @@ class ProcessTransport {
|
|
|
566
623
|
parent_tool_use_id: null,
|
|
567
624
|
...(traceId ? { trace_id: traceId } : {}),
|
|
568
625
|
...(parentSpanId ? { parent_span_id: parentSpanId } : {}),
|
|
626
|
+
...(remoteContext ? { remoteContext } : {}),
|
|
569
627
|
};
|
|
570
628
|
}
|
|
571
629
|
else {
|
|
572
|
-
|
|
573
|
-
if (
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
if (parentSpanId && !message.parent_span_id) {
|
|
577
|
-
message.parent_span_id = parentSpanId;
|
|
630
|
+
const carrier = content;
|
|
631
|
+
if (remoteContext && carrier.remoteContext?.contextId !== undefined
|
|
632
|
+
&& carrier.remoteContext.contextId !== remoteContext.contextId) {
|
|
633
|
+
throw new remote_error_codes_1.RemoteProtocolError('REMOTE_CONTEXT_MISMATCH', 'user message remote context does not match this query');
|
|
578
634
|
}
|
|
635
|
+
message = {
|
|
636
|
+
...content,
|
|
637
|
+
...(traceId && !content.trace_id ? { trace_id: traceId } : {}),
|
|
638
|
+
...(parentSpanId && !content.parent_span_id ? { parent_span_id: parentSpanId } : {}),
|
|
639
|
+
...(remoteContext ? { remoteContext } : {}),
|
|
640
|
+
};
|
|
579
641
|
}
|
|
580
642
|
this.writeLine(message);
|
|
581
643
|
}
|
|
@@ -641,13 +703,14 @@ class ProcessTransport {
|
|
|
641
703
|
/**
|
|
642
704
|
* Send a control error response.
|
|
643
705
|
*/
|
|
644
|
-
sendControlErrorResponse(requestId, error) {
|
|
706
|
+
sendControlErrorResponse(requestId, error, code) {
|
|
645
707
|
const controlResponse = {
|
|
646
708
|
type: 'control_response',
|
|
647
709
|
response: {
|
|
648
710
|
subtype: 'error',
|
|
649
711
|
request_id: requestId,
|
|
650
712
|
error,
|
|
713
|
+
...(code ? { code } : {}),
|
|
651
714
|
},
|
|
652
715
|
};
|
|
653
716
|
this.writeLine(controlResponse);
|
|
@@ -670,6 +733,15 @@ class ProcessTransport {
|
|
|
670
733
|
}
|
|
671
734
|
handlers.add(handler);
|
|
672
735
|
}
|
|
736
|
+
/**
|
|
737
|
+
* 向消息流注入一条 SDK 侧合成的消息(如 mirror_error 帧)。
|
|
738
|
+
* 流未建立或已结束时静默丢弃——此时没有消费者,合成帧无处投递。
|
|
739
|
+
*/
|
|
740
|
+
enqueueMessage(message) {
|
|
741
|
+
if (this.messageStream && !this.messageStream.isFinished) {
|
|
742
|
+
this.messageStream.enqueue(message);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
673
745
|
/**
|
|
674
746
|
* Remove a notification handler for a specific channel.
|
|
675
747
|
*/
|
|
@@ -683,9 +755,16 @@ class ProcessTransport {
|
|
|
683
755
|
}
|
|
684
756
|
}
|
|
685
757
|
/**
|
|
686
|
-
*
|
|
758
|
+
* 关闭 transport。
|
|
759
|
+
*
|
|
760
|
+
* cause 标明终止原因:仅在异常类原因(进程非零退出、退出前未输出 result、
|
|
761
|
+
* stdout 意外关闭、spawn 失败)下才合成一条 error 帧;正常结束与主动关闭均不合成,
|
|
762
|
+
* 避免将成功的 run 误报为失败。
|
|
763
|
+
*
|
|
764
|
+
* 已有调用点(例如上层 Session.close())不传 cause 时按主动关闭处理,
|
|
765
|
+
* 与"手动关闭不产生虚假 error"的既有行为保持一致。
|
|
687
766
|
*/
|
|
688
|
-
close() {
|
|
767
|
+
close(cause = 'explicitClose') {
|
|
689
768
|
if (this.closed) {
|
|
690
769
|
return;
|
|
691
770
|
}
|
|
@@ -731,11 +810,47 @@ class ProcessTransport {
|
|
|
731
810
|
}
|
|
732
811
|
this.sdkMcpTransports.clear();
|
|
733
812
|
this.sdkMcpServerInstances.clear();
|
|
813
|
+
// 注销远程委托 server:reject 在途 pending 并关闭真实 transport
|
|
814
|
+
this.unregisterAllDelegatedServers();
|
|
815
|
+
// 仅在 CLI 未输出终止信号时才合成 error 帧:
|
|
816
|
+
// sawResult / sawError 表示 consumer 已经收到权威的终止信号,
|
|
817
|
+
// explicitClose 属于用户主动关闭,均不应被视为失败。
|
|
818
|
+
const shouldSynthesize = this.messageStream !== null
|
|
819
|
+
&& !this.messageStream.isFinished
|
|
820
|
+
&& !this._sawResult
|
|
821
|
+
&& !this._sawError
|
|
822
|
+
&& ERROR_TERMINAL_CAUSES.has(cause);
|
|
823
|
+
if (shouldSynthesize) {
|
|
824
|
+
const syntheticError = {
|
|
825
|
+
type: 'error',
|
|
826
|
+
error: this._closeReason ?? `Transport closed: ${cause}`,
|
|
827
|
+
...(this._sessionId ? { session_id: this._sessionId } : {}),
|
|
828
|
+
...(this._currentRunId ? { run_id: this._currentRunId } : {}),
|
|
829
|
+
uuid: this.randomUuid(),
|
|
830
|
+
};
|
|
831
|
+
this.messageStream.enqueue(syntheticError);
|
|
832
|
+
}
|
|
734
833
|
// Signal stream completion
|
|
735
834
|
if (this.messageStream) {
|
|
736
835
|
this.messageStream.done();
|
|
737
836
|
}
|
|
738
837
|
}
|
|
838
|
+
/**
|
|
839
|
+
* 为合成帧生成 uuid。旧 Node 运行时可能未内置 WebCrypto,
|
|
840
|
+
* crypto.randomUUID 不可用时降级为"时间戳 + 随机串"的形式。
|
|
841
|
+
*/
|
|
842
|
+
randomUuid() {
|
|
843
|
+
try {
|
|
844
|
+
const c = globalThis.crypto;
|
|
845
|
+
if (c?.randomUUID) {
|
|
846
|
+
return c.randomUUID();
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
catch {
|
|
850
|
+
// fall through
|
|
851
|
+
}
|
|
852
|
+
return `syn-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
853
|
+
}
|
|
739
854
|
// ============= Private Methods =============
|
|
740
855
|
handleLine(line) {
|
|
741
856
|
const trimmed = line.trim();
|
|
@@ -767,6 +882,43 @@ class ProcessTransport {
|
|
|
767
882
|
}
|
|
768
883
|
return;
|
|
769
884
|
}
|
|
885
|
+
// 更新终止态追踪字段:session_id / run_id 用于合成 error 帧时填充上下文,
|
|
886
|
+
// sawResult / sawError 用于 close() 判断是否需要合成补偿帧。
|
|
887
|
+
// 必须在 enqueue 之前更新,否则 consumer 收到首帧后立即退出循环时字段仍为空。
|
|
888
|
+
const framed = message;
|
|
889
|
+
if (framed.session_id && !this._sessionId) {
|
|
890
|
+
this._sessionId = framed.session_id;
|
|
891
|
+
}
|
|
892
|
+
const isRunStartedFrame = framed.type === 'system' && framed.subtype === 'run_started';
|
|
893
|
+
if (isRunStartedFrame && framed.run_id) {
|
|
894
|
+
// run_started 是权威的 run 起点:明确切换到新 run,并重置本 run 的累计状态。
|
|
895
|
+
// 这样即便未来在同一 transport 内跑多个 run,每次 run 的终止态判断都不会串扰。
|
|
896
|
+
if (this._currentRunId !== framed.run_id) {
|
|
897
|
+
this._sawResult = false;
|
|
898
|
+
this._sawError = false;
|
|
899
|
+
}
|
|
900
|
+
this._currentRunId = framed.run_id;
|
|
901
|
+
this._sawRunStarted = true;
|
|
902
|
+
}
|
|
903
|
+
else if (framed.run_id && !this._sawRunStarted) {
|
|
904
|
+
// 未启用 --emit-run-lifecycle 时的回退路径:从任意帧的 run_id 推断当前 run。
|
|
905
|
+
// 一旦见过 run_started,就以其为准,不再被其他帧的 run_id 覆写。
|
|
906
|
+
//
|
|
907
|
+
// 检测到 run_id 变化时,同步重置本 run 的累计终止态(sawResult / sawError),
|
|
908
|
+
// 与 run_started 分支的处理对齐,避免旧 run 的终止信号污染新 run 的判定,
|
|
909
|
+
// 导致 close(cause) 误认为新 run 已正常结束而不合成补偿 error。
|
|
910
|
+
if (this._currentRunId && this._currentRunId !== framed.run_id) {
|
|
911
|
+
this._sawResult = false;
|
|
912
|
+
this._sawError = false;
|
|
913
|
+
}
|
|
914
|
+
this._currentRunId = framed.run_id;
|
|
915
|
+
}
|
|
916
|
+
if (framed.type === 'result') {
|
|
917
|
+
this._sawResult = true;
|
|
918
|
+
}
|
|
919
|
+
else if (framed.type === 'error') {
|
|
920
|
+
this._sawError = true;
|
|
921
|
+
}
|
|
770
922
|
// Only regular messages are enqueued for consumer iteration
|
|
771
923
|
if (this.messageStream) {
|
|
772
924
|
this.messageStream.enqueue(message);
|
|
@@ -797,7 +949,10 @@ class ProcessTransport {
|
|
|
797
949
|
}
|
|
798
950
|
this.pendingRequests.delete(response.request_id);
|
|
799
951
|
if (response.subtype === 'error') {
|
|
800
|
-
|
|
952
|
+
const error = (0, remote_error_codes_1.isRemoteErrorCode)(response.code)
|
|
953
|
+
? new remote_error_codes_1.RemoteProtocolError(response.code, response.error)
|
|
954
|
+
: Object.assign(new Error(response.error), response.code ? { code: response.code } : {});
|
|
955
|
+
pending.reject(error);
|
|
801
956
|
}
|
|
802
957
|
else {
|
|
803
958
|
pending.resolve(response.response ?? {});
|
|
@@ -808,21 +963,27 @@ class ProcessTransport {
|
|
|
808
963
|
// Otherwise use a generic message — no setTimeout needed since
|
|
809
964
|
// exit fires synchronously before close in Node.js child_process.
|
|
810
965
|
this.setCloseReason('CLI process stdout closed unexpectedly');
|
|
811
|
-
this.close();
|
|
966
|
+
this.close('stdoutClosedUnexpectedly');
|
|
812
967
|
}
|
|
813
968
|
handleError(error) {
|
|
814
969
|
this.setCloseReason(`CLI I/O error: ${error.message}`);
|
|
815
|
-
this.close();
|
|
970
|
+
this.close('stdoutClosedUnexpectedly');
|
|
816
971
|
}
|
|
817
972
|
handleProcessError(error) {
|
|
818
973
|
this.setCloseReason(`CLI process spawn error: ${error.message}`);
|
|
819
|
-
this.close();
|
|
974
|
+
this.close('spawnError');
|
|
820
975
|
}
|
|
821
976
|
handleProcessExit(code) {
|
|
822
977
|
const stderr = this._stderrBuffer.trim();
|
|
823
978
|
const stderrSuffix = stderr ? ` | stderr: ${stderr.slice(-500)}` : '';
|
|
824
979
|
this.setCloseReason(`CLI process exited with code ${code}${stderrSuffix}`);
|
|
825
|
-
|
|
980
|
+
// 退出码为 0 且已观察到 result → 正常结束;
|
|
981
|
+
// 退出码为 0 但未观察到 result → CLI 在输出终止信号前提前退出,按异常处理以便下游归因;
|
|
982
|
+
// 退出码非 0 → 一律按异常处理。
|
|
983
|
+
const cause = code === 0
|
|
984
|
+
? (this._sawResult ? 'sawResult' : 'processExitZeroBeforeResult')
|
|
985
|
+
: 'processExitNonZero';
|
|
986
|
+
this.close(cause);
|
|
826
987
|
}
|
|
827
988
|
setCloseReason(reason) {
|
|
828
989
|
if (!this._closeReason) {
|
|
@@ -952,6 +1113,28 @@ class ProcessTransport {
|
|
|
952
1113
|
const { server_name, message } = mcpRequest;
|
|
953
1114
|
const transport = this.sdkMcpTransports.get(server_name);
|
|
954
1115
|
if (!transport) {
|
|
1116
|
+
// in-process 未命中:尝试远程委托 bridge 回退(control ack 仍在本方法包装,不进 bridge)
|
|
1117
|
+
const scopedKey = this.resolveDelegatedScopedKey(server_name);
|
|
1118
|
+
if (this.managedRemoteMcpBridge && scopedKey && this.managedRemoteMcpBridge.isRegistered(scopedKey)) {
|
|
1119
|
+
try {
|
|
1120
|
+
const anyMsg = message;
|
|
1121
|
+
const isRequest = 'id' in anyMsg && anyMsg.id !== null && anyMsg.id !== undefined;
|
|
1122
|
+
const result = await this.managedRemoteMcpBridge.handleMcpMessage(scopedKey, message);
|
|
1123
|
+
if (isRequest) {
|
|
1124
|
+
this.sendControlResponse(request.request_id, { mcp_response: result });
|
|
1125
|
+
}
|
|
1126
|
+
else {
|
|
1127
|
+
// notification:回 id-less sentinel 释放 CLI control pending
|
|
1128
|
+
this.sendControlResponse(request.request_id, {
|
|
1129
|
+
mcp_response: { jsonrpc: '2.0', result: {} },
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
catch (error) {
|
|
1134
|
+
this.sendControlErrorResponse(request.request_id, error instanceof Error ? error.message : String(error));
|
|
1135
|
+
}
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
955
1138
|
this.sendControlErrorResponse(request.request_id, `SDK MCP server not found: ${server_name}`);
|
|
956
1139
|
return;
|
|
957
1140
|
}
|
|
@@ -978,6 +1161,103 @@ class ProcessTransport {
|
|
|
978
1161
|
this.sendControlErrorResponse(request.request_id, error instanceof Error ? error.message : String(error));
|
|
979
1162
|
}
|
|
980
1163
|
}
|
|
1164
|
+
/**
|
|
1165
|
+
* 注册远程委托 server(宿主驱动)。
|
|
1166
|
+
*
|
|
1167
|
+
* 首次调用时懒构造 ManagedRemoteMcpBridge:createTransport 由受管层注入(解析真实
|
|
1168
|
+
* endpoint/token),redactResponse 接线为 redactDelegatedResponse(响应离开宿主回到 CLI
|
|
1169
|
+
* 之前先脱敏)。随后对每个 descriptor 用与路由一致的 resolveDelegatedScopedKey 生成 scoped
|
|
1170
|
+
* key 并注册,key 记入集合供 close 统一注销。
|
|
1171
|
+
*
|
|
1172
|
+
* 接线现状:ProcessTransport 构造期在 `delegatedMcpServers` + `mcpTransportFactory` 齐备时
|
|
1173
|
+
* 自动调用本方法(host-driven v1 必做)。`unregisterAllDelegatedServers` 已接入
|
|
1174
|
+
* close 兜底,control connection 关闭时统一释放 transport 与 pending。
|
|
1175
|
+
*/
|
|
1176
|
+
registerDelegatedServers(descriptors, createTransport) {
|
|
1177
|
+
if (!this.managedRemoteMcpBridge) {
|
|
1178
|
+
// 审计出口:有 stderr 回调则写入(仅非 secret 的结构化事件),否则忽略。
|
|
1179
|
+
const onAudit = (event) => {
|
|
1180
|
+
this.options.stderr?.(`[mcp-delegation-audit] ${JSON.stringify(event)}\n`);
|
|
1181
|
+
};
|
|
1182
|
+
this.managedRemoteMcpBridge = new managed_remote_mcp_bridge_1.ManagedRemoteMcpBridge({
|
|
1183
|
+
createTransport,
|
|
1184
|
+
requestTimeoutMs: this.options.requestTimeoutMs ?? 30000,
|
|
1185
|
+
// 组合脱敏:先做 schema fail-close 筛查(剔除含凭证形参的 tool),再做值级脱敏;
|
|
1186
|
+
// 被拒 tool 经 onAudit 上报,不抛异常。
|
|
1187
|
+
redactResponse: (scopedKey, message) => {
|
|
1188
|
+
const { message: safe, rejected } = (0, delegated_redaction_1.redactAndScreenDelegatedResponse)(message);
|
|
1189
|
+
if (rejected.length > 0) {
|
|
1190
|
+
onAudit({ type: 'delegated_tool_schema_rejected', scopedKey, rejected });
|
|
1191
|
+
}
|
|
1192
|
+
return safe;
|
|
1193
|
+
},
|
|
1194
|
+
onAudit,
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
for (const descriptor of descriptors) {
|
|
1198
|
+
const scopedKey = this.resolveDelegatedScopedKey(descriptor.serverName);
|
|
1199
|
+
if (!scopedKey) {
|
|
1200
|
+
continue;
|
|
1201
|
+
}
|
|
1202
|
+
this.managedRemoteMcpBridge.registerDelegatedServer(scopedKey, {
|
|
1203
|
+
transport: descriptor.transport,
|
|
1204
|
+
capabilityId: descriptor.capabilityId,
|
|
1205
|
+
});
|
|
1206
|
+
this.registeredDelegatedScopedKeys.add(scopedKey);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* 注销所有已注册的委托 server(reject 在途 pending、关闭真实 transport)。
|
|
1211
|
+
* 由 close 调用;单个注销失败不阻塞其他。
|
|
1212
|
+
*/
|
|
1213
|
+
unregisterAllDelegatedServers() {
|
|
1214
|
+
if (!this.managedRemoteMcpBridge) {
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
for (const scopedKey of this.registeredDelegatedScopedKeys) {
|
|
1218
|
+
this.managedRemoteMcpBridge.unregisterDelegatedServer(scopedKey).catch(() => {
|
|
1219
|
+
// 清理阶段忽略单个注销错误
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
this.registeredDelegatedScopedKeys.clear();
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* 组合远程委托 server 的 scoped key。
|
|
1226
|
+
*
|
|
1227
|
+
* 完整 scoped key 语义是 control connection / tenant / user / session / run + serverName。
|
|
1228
|
+
* 本 transport 从 ProcessTransportOptions 拿到 controlConnectionId /
|
|
1229
|
+
* tenantId / userId / sessionId / runId,按字符集校验后用分隔符 `::` 拼接,缺失维度直接跳过
|
|
1230
|
+
* (不写空段,避免 `::` 退化成空字符串导致跨 scope 撞 key)。所有维度都缺时才退化为裸
|
|
1231
|
+
* serverName——这是最后兜底,部署方应至少注入 controlConnectionId / sessionId 之一。
|
|
1232
|
+
* 注册与路由必须复用这同一组合逻辑,两侧的 key 才能一致。
|
|
1233
|
+
*/
|
|
1234
|
+
resolveDelegatedScopedKey(serverName) {
|
|
1235
|
+
const segments = [];
|
|
1236
|
+
const push = (v, label) => {
|
|
1237
|
+
if (typeof v === 'string' && v.length > 0) {
|
|
1238
|
+
if (!/^[A-Za-z0-9_.:-]+$/.test(v)) {
|
|
1239
|
+
// 非法字符:跳过该维度并记审计,避免拼出含分隔符冲突的 scoped key。
|
|
1240
|
+
this.options.stderr?.(`[mcp-delegation-audit] scoped key segment "${label}" has invalid chars, skipped\n`);
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
segments.push(v);
|
|
1244
|
+
}
|
|
1245
|
+
};
|
|
1246
|
+
push(this.options.controlConnectionId, 'controlConnectionId');
|
|
1247
|
+
push(this.options.tenantId, 'tenantId');
|
|
1248
|
+
push(this.options.userId, 'userId');
|
|
1249
|
+
push(this.options.sessionId, 'sessionId');
|
|
1250
|
+
push(this.options.runId, 'runId');
|
|
1251
|
+
push(serverName, 'serverName');
|
|
1252
|
+
if (segments.length === 0) {
|
|
1253
|
+
return undefined;
|
|
1254
|
+
}
|
|
1255
|
+
if (segments.length === 1) {
|
|
1256
|
+
// 仅 serverName 可用:兜底返回裸 serverName(部署方未注入 scope 维度的退化场景)。
|
|
1257
|
+
return serverName;
|
|
1258
|
+
}
|
|
1259
|
+
return segments.join('::');
|
|
1260
|
+
}
|
|
981
1261
|
/**
|
|
982
1262
|
* Handle MCP request and wait for response from MCP server.
|
|
983
1263
|
*/
|