@ai-workbench/remote-agent 0.8.28 → 0.9.0-toolbridge.2
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/README.md +13 -3
- package/dist/acp.d.ts +3 -0
- package/dist/acp.js +35 -7
- package/dist/agents.js +1 -1
- package/dist/broker-ownership.d.ts +10 -0
- package/dist/broker-ownership.js +59 -0
- package/dist/broker-runtime.js +5 -2
- package/dist/claude-local.js +2 -1
- package/dist/employee-mcp.d.ts +2 -0
- package/dist/employee-mcp.js +3 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.js +108 -16
- package/dist/presentation.js +3 -0
- package/dist/provider.d.ts +4 -0
- package/dist/toolbridge-loop.d.ts +19 -0
- package/dist/toolbridge-loop.js +251 -0
- package/dist/toolbridge-table.d.ts +10 -0
- package/dist/toolbridge-table.js +27 -0
- package/dist/toolbridge.d.ts +114 -0
- package/dist/toolbridge.js +205 -0
- package/package.json +6 -5
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { NeedError } from "./remote.js";
|
|
3
|
+
/** Fixed official tool names; no description heuristics or plugin-name branches. */
|
|
4
|
+
export const TOOLBRIDGE_EXCLUDED = Object.freeze([
|
|
5
|
+
'read', 'write', 'edit', 'read_image', 'glob', 'grep', 'bash', 'pwsh', 'str_replace_editor',
|
|
6
|
+
'job_output', 'job_list', 'job_kill', 'load_workspace_dependencies',
|
|
7
|
+
'terminal_open', 'terminal_send', 'terminal_read', 'terminal_signal', 'terminal_close', 'terminal_list',
|
|
8
|
+
'subagent', 'list_subagent_models', 'send_message', 'interrupt_agent', 'spawn_teammate', 'list_agents', 'wait_agent',
|
|
9
|
+
'team_task_create', 'team_task_list', 'team_task_get', 'team_task_update', 'run_code',
|
|
10
|
+
]);
|
|
11
|
+
const excluded = new Set(TOOLBRIDGE_EXCLUDED);
|
|
12
|
+
export function bridgeTable(tools = []) {
|
|
13
|
+
const table = new Map(), names = new Set();
|
|
14
|
+
for (const tool of tools) {
|
|
15
|
+
if (excluded.has(tool.name))
|
|
16
|
+
continue;
|
|
17
|
+
if (names.has(tool.name))
|
|
18
|
+
throw new NeedError('TOOLBRIDGE_TOOL_DUPLICATE', '当前工具表存在重名,工具桥未开始。', '官方唯一工具名称');
|
|
19
|
+
names.add(tool.name);
|
|
20
|
+
const alias = 'dsh_' + createHash('sha256').update(tool.name).digest('hex').slice(0, 32);
|
|
21
|
+
if (table.has(alias))
|
|
22
|
+
throw new NeedError('TOOLBRIDGE_NAME_COLLISION', '工具桥名称冲突,未执行。', '唯一工具桥名称');
|
|
23
|
+
const schema = structuredClone(tool);
|
|
24
|
+
table.set(alias, { alias, schema, wire: { name: alias, description: 'DSH工具 ' + schema.name + ':' + schema.description, inputSchema: structuredClone(schema.parameters) } });
|
|
25
|
+
}
|
|
26
|
+
return table;
|
|
27
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { type CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import type { GenerateOptions, ToolCallBlock } from '@deepseek-ai/dsh-llm';
|
|
3
|
+
import type { SessionUpdate } from '@agentclientprotocol/sdk/unstable';
|
|
4
|
+
import { type BridgeTool } from './toolbridge-table.ts';
|
|
5
|
+
import type { ImageReader } from './input.ts';
|
|
6
|
+
import type { DetailObservation } from './conversation-details.ts';
|
|
7
|
+
export declare const TOOLBRIDGE_TIMEOUT_SECONDS = 1800;
|
|
8
|
+
export declare const TOOLBRIDGE_NOTICE = "\u542B\u5DE5\u5177\u6865\uFF1A\u672C\u673A\u6587\u4EF6\u5939\u7684\u6269\u5C55\u5DE5\u5177\u7531DSH\u6267\u884C\u5E76\u6309\u5F53\u524D\u6743\u9650\u5BA1\u6279\uFF1B\u6587\u4EF6\u548C\u547D\u4EE4\u4FDD\u6301\u539F\u6267\u884C\u65B9\u5F0F\u3002\u5DE5\u5177\u8868\u5728\u65B0\u4E00\u8F6E\u4E3B\u673A\u8FDE\u63A5\u65F6\u66F4\u65B0\u3002";
|
|
9
|
+
export declare const TOOLBRIDGE_ENDED = "\u4E3B\u673A\u5DF2\u7ED3\u675F\u8FD9\u6B21\u5DE5\u5177\u7B49\u5F85\uFF1B\u672C\u8F6E\u5DF2\u505C\u6B62\uFF0C\u8FDF\u5230\u7ED3\u679C\u4E0D\u4F1A\u4EA4\u56DE\uFF0C\u4E5F\u4E0D\u4F1A\u91CD\u65B0\u6267\u884C\u3002\u8BF7\u6838\u5BF9\u5DF2\u53D1\u751F\u7684\u64CD\u4F5C\u3002";
|
|
10
|
+
interface Pending {
|
|
11
|
+
block: ToolCallBlock;
|
|
12
|
+
alias: string;
|
|
13
|
+
reply: ReturnType<typeof Promise.withResolvers<CallToolResult>>;
|
|
14
|
+
exported: boolean;
|
|
15
|
+
settled: boolean;
|
|
16
|
+
off(): void;
|
|
17
|
+
}
|
|
18
|
+
/** Only a protocol queue. Tool execution and authorization belong to DSH. */
|
|
19
|
+
export declare class ToolBridge {
|
|
20
|
+
private options;
|
|
21
|
+
private readImage?;
|
|
22
|
+
readonly controller: AbortController;
|
|
23
|
+
private snapshot?;
|
|
24
|
+
get table(): ReadonlyMap<string, BridgeTool>;
|
|
25
|
+
readonly calls: Map<string, Pending>;
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
ended: boolean;
|
|
28
|
+
notice?: string;
|
|
29
|
+
replayState?: {
|
|
30
|
+
response: unknown;
|
|
31
|
+
};
|
|
32
|
+
localTurnEnded: boolean;
|
|
33
|
+
concludeAcknowledged: boolean;
|
|
34
|
+
onConcludeAcknowledged: () => void;
|
|
35
|
+
private nativeCompleted;
|
|
36
|
+
private nativeTerminated;
|
|
37
|
+
private concludeNative;
|
|
38
|
+
beginConclude(): void;
|
|
39
|
+
private active?;
|
|
40
|
+
begin(signal: AbortSignal): void;
|
|
41
|
+
end(): void;
|
|
42
|
+
changed: () => void;
|
|
43
|
+
record: (event: DetailObservation) => void;
|
|
44
|
+
onAbandon: (notice: string) => void;
|
|
45
|
+
private nativeBridgeIds;
|
|
46
|
+
constructor(options: GenerateOptions, readImage?: ImageReader | undefined);
|
|
47
|
+
enable(record: (event: DetailObservation) => void): void;
|
|
48
|
+
get tools(): {
|
|
49
|
+
inputSchema: {
|
|
50
|
+
[x: string]: unknown;
|
|
51
|
+
type: "object";
|
|
52
|
+
properties?: {
|
|
53
|
+
[x: string]: object;
|
|
54
|
+
} | undefined;
|
|
55
|
+
required?: string[] | undefined;
|
|
56
|
+
};
|
|
57
|
+
name: string;
|
|
58
|
+
description?: string | undefined;
|
|
59
|
+
outputSchema?: {
|
|
60
|
+
[x: string]: unknown;
|
|
61
|
+
type: "object";
|
|
62
|
+
properties?: {
|
|
63
|
+
[x: string]: object;
|
|
64
|
+
} | undefined;
|
|
65
|
+
required?: string[] | undefined;
|
|
66
|
+
} | undefined;
|
|
67
|
+
annotations?: {
|
|
68
|
+
title?: string | undefined;
|
|
69
|
+
readOnlyHint?: boolean | undefined;
|
|
70
|
+
destructiveHint?: boolean | undefined;
|
|
71
|
+
idempotentHint?: boolean | undefined;
|
|
72
|
+
openWorldHint?: boolean | undefined;
|
|
73
|
+
} | undefined;
|
|
74
|
+
execution?: {
|
|
75
|
+
taskSupport?: "optional" | "required" | "forbidden" | undefined;
|
|
76
|
+
} | undefined;
|
|
77
|
+
_meta?: {
|
|
78
|
+
[x: string]: unknown;
|
|
79
|
+
} | undefined;
|
|
80
|
+
icons?: {
|
|
81
|
+
src: string;
|
|
82
|
+
mimeType?: string | undefined;
|
|
83
|
+
sizes?: string[] | undefined;
|
|
84
|
+
theme?: "light" | "dark" | undefined;
|
|
85
|
+
}[] | undefined;
|
|
86
|
+
title?: string | undefined;
|
|
87
|
+
}[];
|
|
88
|
+
isBridge(name: unknown): boolean;
|
|
89
|
+
call(alias: string, args: Record<string, unknown>, signal: AbortSignal): Promise<CallToolResult>;
|
|
90
|
+
get pending(): Pending[];
|
|
91
|
+
get unsettled(): boolean;
|
|
92
|
+
get waiting(): boolean;
|
|
93
|
+
results(options: GenerateOptions): Promise<void>;
|
|
94
|
+
private mapResult;
|
|
95
|
+
private concludeAck;
|
|
96
|
+
observe(update: SessionUpdate): void;
|
|
97
|
+
/** A failed native bridge cancels the entire parked round; never guess between parallel identical calls. */
|
|
98
|
+
abandon(reason: string): void;
|
|
99
|
+
close(): void;
|
|
100
|
+
}
|
|
101
|
+
/** Codex executes nc in its existing employee environment, never on the host. */
|
|
102
|
+
export declare function openToolBridgeMcp(bridge: ToolBridge, maxBytes: number): Promise<{
|
|
103
|
+
mcpServers: {
|
|
104
|
+
name: string;
|
|
105
|
+
command: string;
|
|
106
|
+
args: string[];
|
|
107
|
+
env: {
|
|
108
|
+
name: string;
|
|
109
|
+
value: string;
|
|
110
|
+
}[];
|
|
111
|
+
}[];
|
|
112
|
+
close: () => Promise<void>;
|
|
113
|
+
}>;
|
|
114
|
+
export {};
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { createServer } from 'node:net';
|
|
3
|
+
import { mkdtemp, lstat, rm } from 'node:fs/promises';
|
|
4
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
5
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
+
import { EmployeeSocketTransport } from "./employee-mcp.js";
|
|
7
|
+
import { bridgeTable } from "./toolbridge-table.js";
|
|
8
|
+
import { NeedError } from "./remote.js";
|
|
9
|
+
export const TOOLBRIDGE_TIMEOUT_SECONDS = 1800;
|
|
10
|
+
export const TOOLBRIDGE_NOTICE = '含工具桥:本机文件夹的扩展工具由DSH执行并按当前权限审批;文件和命令保持原执行方式。工具表在新一轮主机连接时更新。';
|
|
11
|
+
export const TOOLBRIDGE_ENDED = '主机已结束这次工具等待;本轮已停止,迟到结果不会交回,也不会重新执行。请核对已发生的操作。';
|
|
12
|
+
const errorResult = (text) => ({ isError: true, content: [{ type: 'text', text }] });
|
|
13
|
+
/** Only a protocol queue. Tool execution and authorization belong to DSH. */
|
|
14
|
+
export class ToolBridge {
|
|
15
|
+
options;
|
|
16
|
+
readImage;
|
|
17
|
+
controller = new AbortController();
|
|
18
|
+
snapshot;
|
|
19
|
+
get table() { return this.snapshot ??= bridgeTable(this.options.tools); }
|
|
20
|
+
calls = new Map();
|
|
21
|
+
enabled = false;
|
|
22
|
+
ended = false;
|
|
23
|
+
notice;
|
|
24
|
+
replayState;
|
|
25
|
+
localTurnEnded = false;
|
|
26
|
+
concludeAcknowledged = false;
|
|
27
|
+
onConcludeAcknowledged = () => { };
|
|
28
|
+
nativeCompleted = new Set();
|
|
29
|
+
nativeTerminated = new Set();
|
|
30
|
+
concludeNative = new Set();
|
|
31
|
+
beginConclude() { this.localTurnEnded = true; this.concludeNative = new Set([...this.nativeBridgeIds].filter(id => !this.nativeTerminated.has(id))); }
|
|
32
|
+
active;
|
|
33
|
+
begin(signal) { this.active = signal; }
|
|
34
|
+
end() { this.active = undefined; }
|
|
35
|
+
changed = () => { };
|
|
36
|
+
record = () => { };
|
|
37
|
+
onAbandon = () => { };
|
|
38
|
+
nativeBridgeIds = new Set();
|
|
39
|
+
constructor(options, readImage) {
|
|
40
|
+
this.options = options;
|
|
41
|
+
this.readImage = readImage;
|
|
42
|
+
}
|
|
43
|
+
enable(record) { if (!this.table.size)
|
|
44
|
+
return; this.enabled = true; this.record = record; record({ kind: 'session', severity: 'info', code: 'TOOLBRIDGE_SELECTED', summary: TOOLBRIDGE_NOTICE, payload: { toolCount: this.table.size, toolTimeoutSec: TOOLBRIDGE_TIMEOUT_SECONDS, timeoutSource: 'owner-2026-10-26', snapshot: 'native-session-open', excluded: 'official-file-command-subagent-team-run_code' } }); }
|
|
45
|
+
get tools() { return [...this.table.values()].map(t => t.wire); }
|
|
46
|
+
isBridge(name) { return typeof name === 'string' && this.table.has(name.replace(/^mcp__employee__/, '')); }
|
|
47
|
+
async call(alias, args, signal) {
|
|
48
|
+
const tool = this.table.get(alias);
|
|
49
|
+
if (!this.enabled || this.ended || this.localTurnEnded || !this.active || this.active.aborted || !tool || signal.aborted)
|
|
50
|
+
return errorResult('工具桥当前不可用,未执行。');
|
|
51
|
+
const id = ('awb-' + randomUUID());
|
|
52
|
+
const abort = () => this.abandon('MCP_REQUEST_CANCELLED');
|
|
53
|
+
const pending = { block: { type: 'tool-call', id, name: tool.schema.name, arguments: JSON.stringify(args) }, alias, reply: Promise.withResolvers(), exported: false, settled: false, off: () => signal.removeEventListener('abort', abort) };
|
|
54
|
+
this.calls.set(id, pending);
|
|
55
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
56
|
+
this.record({ kind: 'tool', severity: 'info', code: 'TOOLBRIDGE_CALL_PARKED', summary: '主机工具调用交给DSH执行与审批,原prompt继续等待。', payload: { callId: id, tool: tool.schema.name } });
|
|
57
|
+
this.changed();
|
|
58
|
+
if (signal.aborted)
|
|
59
|
+
abort();
|
|
60
|
+
return pending.reply.promise;
|
|
61
|
+
}
|
|
62
|
+
get pending() { return [...this.calls.values()].filter(p => !p.settled && !p.exported); }
|
|
63
|
+
get unsettled() { return [...this.calls.values()].some(p => !p.settled); }
|
|
64
|
+
get waiting() { return [...this.calls.values()].some(p => p.exported && !p.settled); }
|
|
65
|
+
async results(options) {
|
|
66
|
+
const batch = [];
|
|
67
|
+
for (const p of this.calls.values()) {
|
|
68
|
+
if (!p.exported || p.settled)
|
|
69
|
+
continue;
|
|
70
|
+
const matches = options.messages.filter((m) => m.role === 'tool' && m.toolCallId === p.block.id);
|
|
71
|
+
if (matches.length !== 1)
|
|
72
|
+
throw new NeedError('TOOLBRIDGE_RESULT_MISSING', '工具桥没有收到唯一匹配的DSH结果,本轮已停止;不会重跑。', '原toolCallId对应的官方tool结果');
|
|
73
|
+
batch.push({ pending: p, result: await this.mapResult(matches[0], options.signal) });
|
|
74
|
+
}
|
|
75
|
+
// Map the whole DSH batch before releasing any native Promise. A normal MCP
|
|
76
|
+
// isError must not race an unfinished image mapping from this same batch.
|
|
77
|
+
if (this.ended) {
|
|
78
|
+
for (const { pending: p } of batch)
|
|
79
|
+
this.record({ kind: 'tool', severity: 'warning', code: 'DSH_BRIDGE_LATE_RESULT_DROPPED', summary: TOOLBRIDGE_ENDED, payload: { callId: p.block.id } });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (this.localTurnEnded)
|
|
83
|
+
for (const p of this.calls.values())
|
|
84
|
+
if (!p.exported && !p.settled) {
|
|
85
|
+
batch.push({ pending: p, result: errorResult('DSH工具已结束本轮,这个尚未派发的调用没有执行。') });
|
|
86
|
+
this.record({ kind: 'tool', severity: 'info', code: 'TOOLBRIDGE_CONCLUDED_UNDISPATCHED', summary: 'DSH工具结束了本轮,尚未交给DSH的调用没有执行。', payload: { callId: p.block.id } });
|
|
87
|
+
}
|
|
88
|
+
const ready = batch.filter(row => !row.pending.settled);
|
|
89
|
+
for (const { pending: p } of ready) {
|
|
90
|
+
p.settled = true;
|
|
91
|
+
p.off();
|
|
92
|
+
}
|
|
93
|
+
for (const { pending: p, result } of ready)
|
|
94
|
+
p.reply.resolve(result);
|
|
95
|
+
this.concludeAck();
|
|
96
|
+
}
|
|
97
|
+
async mapResult(message, signal) {
|
|
98
|
+
const content = [];
|
|
99
|
+
for (const block of message.content) {
|
|
100
|
+
if (block.type === 'text') {
|
|
101
|
+
content.push({ type: 'text', text: block.text });
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (block.type === 'image') {
|
|
105
|
+
if (block.offloaded)
|
|
106
|
+
return errorResult('这张图片已按DSH策略移出上下文,工具桥未将占位内容冒充图片结果。');
|
|
107
|
+
if (!this.readImage)
|
|
108
|
+
throw new NeedError('ATTACHMENT_REQUIRED', '我需要官方附件组件读取工具返回的图片。', 'attachments.readImage');
|
|
109
|
+
const image = await this.readImage(block.attachment, signal);
|
|
110
|
+
signal?.throwIfAborted();
|
|
111
|
+
if (image.ref.attachmentId !== block.attachment.attachmentId || image.ref.bytes !== image.data.byteLength || !['image/png', 'image/jpeg', 'image/webp', 'image/gif'].includes(image.ref.mediaType))
|
|
112
|
+
throw new NeedError('ATTACHMENT_INVALID', '工具图片的附件内容无法核对,本轮没有交回。', '官方核验的图片字节与媒体类型');
|
|
113
|
+
content.push({ type: 'image', mimeType: image.ref.mediaType, data: Buffer.from(image.data).toString('base64') });
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
return errorResult('工具返回了本版工具桥尚不支持的内容类型:' + block.type + ';没有静默丢弃或重新执行。');
|
|
117
|
+
}
|
|
118
|
+
return { content, isError: !!message.isError };
|
|
119
|
+
}
|
|
120
|
+
concludeAck() {
|
|
121
|
+
if (this.localTurnEnded && !this.unsettled && !this.concludeAcknowledged && this.concludeNative.size && [...this.concludeNative].every(id => this.nativeCompleted.has(id))) {
|
|
122
|
+
this.concludeAcknowledged = true;
|
|
123
|
+
this.record({ kind: 'tool', severity: 'info', code: 'TOOLBRIDGE_CONCLUDED_RESULT_DELIVERED', summary: 'DSH工具结束了本轮,原生工具已确认收到结果;结束原prompt,不再派发新工具。' });
|
|
124
|
+
this.onConcludeAcknowledged();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
observe(update) {
|
|
128
|
+
if (!this.enabled || this.ended)
|
|
129
|
+
return;
|
|
130
|
+
if (update.sessionUpdate !== 'tool_call' && update.sessionUpdate !== 'tool_call_update')
|
|
131
|
+
return;
|
|
132
|
+
const u = update;
|
|
133
|
+
const raw = u.rawInput;
|
|
134
|
+
const name = u.name ?? u._meta?.claudeCode?.toolName;
|
|
135
|
+
if (this.isBridge(name) || raw?.server === 'employee' && this.isBridge(raw.tool) || this.isBridge(u.title))
|
|
136
|
+
this.nativeBridgeIds.add(u.toolCallId);
|
|
137
|
+
if ((u.status === 'completed' || u.status === 'failed') && this.nativeBridgeIds.has(u.toolCallId))
|
|
138
|
+
this.nativeTerminated.add(u.toolCallId);
|
|
139
|
+
if (u.status === 'failed' && this.nativeBridgeIds.has(u.toolCallId) && !this.unsettled && u.rawOutput?.result?.isError === true && Array.isArray(u.rawOutput.result.content)) {
|
|
140
|
+
this.nativeCompleted.add(u.toolCallId);
|
|
141
|
+
this.concludeAck();
|
|
142
|
+
}
|
|
143
|
+
if (u.status === 'completed' && this.nativeBridgeIds.has(u.toolCallId)) {
|
|
144
|
+
this.nativeCompleted.add(u.toolCallId);
|
|
145
|
+
this.concludeAck();
|
|
146
|
+
}
|
|
147
|
+
if (u.status === 'failed' && this.nativeBridgeIds.has(u.toolCallId) && (!this.localTurnEnded || this.concludeNative.has(u.toolCallId)) && this.unsettled)
|
|
148
|
+
this.abandon('HOST_TOOL_FAILED');
|
|
149
|
+
}
|
|
150
|
+
/** A failed native bridge cancels the entire parked round; never guess between parallel identical calls. */
|
|
151
|
+
abandon(reason) {
|
|
152
|
+
if (this.ended)
|
|
153
|
+
return;
|
|
154
|
+
this.notice = TOOLBRIDGE_ENDED;
|
|
155
|
+
this.record({ kind: 'tool', severity: 'warning', code: 'DSH_BRIDGE_CALL_ABANDONED_BY_HOST', summary: this.notice, payload: { reason, toolTimeoutSec: TOOLBRIDGE_TIMEOUT_SECONDS, fixedTimeoutReasonAvailable: false, scope: 'entire-parked-round', replayed: false } });
|
|
156
|
+
this.close();
|
|
157
|
+
this.onAbandon(this.notice);
|
|
158
|
+
}
|
|
159
|
+
close() {
|
|
160
|
+
if (this.ended)
|
|
161
|
+
return;
|
|
162
|
+
this.ended = true;
|
|
163
|
+
this.controller.abort();
|
|
164
|
+
for (const p of this.calls.values())
|
|
165
|
+
if (!p.settled) {
|
|
166
|
+
p.settled = true;
|
|
167
|
+
p.off();
|
|
168
|
+
p.reply.resolve(errorResult('本轮工具等待已结束;结果不重放。'));
|
|
169
|
+
}
|
|
170
|
+
this.changed();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** Codex executes nc in its existing employee environment, never on the host. */
|
|
174
|
+
export async function openToolBridgeMcp(bridge, maxBytes) {
|
|
175
|
+
const root = await mkdtemp('/private/tmp/aw-tb-'), socketPath = root + '/mcp';
|
|
176
|
+
const clients = new Set();
|
|
177
|
+
let closed = false;
|
|
178
|
+
const listener = createServer(socket => {
|
|
179
|
+
if (closed) {
|
|
180
|
+
socket.destroy();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const server = new Server({ name: 'ai-workbench-toolbridge', version: '0.9.0' }, { capabilities: { tools: {} } });
|
|
184
|
+
clients.add(server);
|
|
185
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: bridge.tools }));
|
|
186
|
+
server.setRequestHandler(CallToolRequestSchema, (r, extra) => bridge.call(r.params.name, r.params.arguments ?? {}, extra.signal));
|
|
187
|
+
server.onclose = () => clients.delete(server);
|
|
188
|
+
server.onerror = () => bridge.abandon('MCP_TRANSPORT_FAILED');
|
|
189
|
+
void server.connect(new EmployeeSocketTransport(socket, maxBytes)).catch(() => { socket.destroy(); bridge.abandon('MCP_CONNECT_FAILED'); });
|
|
190
|
+
});
|
|
191
|
+
const close = async () => { if (closed)
|
|
192
|
+
return; closed = true; bridge.close(); await Promise.allSettled([...clients].map(c => c.close())); if (listener.listening)
|
|
193
|
+
await new Promise(r => listener.close(() => r())); await rm(root, { recursive: true }); };
|
|
194
|
+
try {
|
|
195
|
+
const stat = await lstat(root);
|
|
196
|
+
if (stat.uid !== process.getuid?.() || (stat.mode & 0o077) !== 0)
|
|
197
|
+
throw Error('TOOLBRIDGE_PRIVATE_DIRECTORY_REQUIRED');
|
|
198
|
+
await new Promise((yes, no) => { listener.once('error', no); listener.listen(socketPath, () => { listener.removeListener('error', no); yes(); }); });
|
|
199
|
+
return { mcpServers: [{ name: 'employee', command: '/usr/bin/nc', args: ['-U', socketPath], env: [] }], close };
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
await close();
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-workbench/remote-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0-toolbridge.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"zod": "4.6.7"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@ai-workbench/connect": "file:test/fixtures/ai-workbench-connect-0.7.
|
|
39
|
+
"@ai-workbench/connect": "file:test/fixtures/ai-workbench-connect-0.7.37.tgz",
|
|
40
40
|
"@ai-workbench/network": "file:test/fixtures/ai-workbench-network-0.4.9.tgz",
|
|
41
41
|
"@deepseek-ai/cordis": "4.0.4",
|
|
42
42
|
"@deepseek-ai/cordis-plugin-group": "1.0.4",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"@deepseek-ai/dsh-storage-domain": "0.2.0-rc.2"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"@ai-workbench/connect": "~0.7.
|
|
70
|
+
"@ai-workbench/connect": "~0.7.37",
|
|
71
71
|
"@ai-workbench/network": "~0.4.9",
|
|
72
72
|
"@deepseek-ai/cordis": "4.0.4",
|
|
73
73
|
"@deepseek-ai/dsh-agent": "0.2.0-rc.2",
|
|
@@ -102,10 +102,11 @@
|
|
|
102
102
|
"optional": true
|
|
103
103
|
}
|
|
104
104
|
},
|
|
105
|
-
"description": "
|
|
105
|
+
"description": "AI 写作工作台(含工具桥):远程Claude与Codex,本机目录工具由DSH执行与审批。",
|
|
106
106
|
"publishConfig": {
|
|
107
107
|
"access": "public",
|
|
108
|
-
"registry": "https://registry.npmjs.org/"
|
|
108
|
+
"registry": "https://registry.npmjs.org/",
|
|
109
|
+
"tag": "toolbridge"
|
|
109
110
|
},
|
|
110
111
|
"keywords": [
|
|
111
112
|
"dsh",
|