@ai-workbench/remote-agent 0.8.2 → 0.8.10
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 +42 -7
- package/dist/acp.d.ts +46 -1
- package/dist/acp.js +296 -36
- package/dist/approval-label.d.ts +6 -0
- package/dist/approval-label.js +57 -0
- package/dist/broker-runtime.d.ts +10 -0
- package/dist/broker-runtime.js +334 -0
- package/dist/broker.d.ts +29 -6
- package/dist/broker.js +146 -142
- package/dist/claude-local.d.ts +37 -0
- package/dist/claude-local.js +207 -0
- package/dist/client.js +71 -2
- package/dist/conversation-details.d.ts +1 -1
- package/dist/conversation-details.js +88 -8
- package/dist/employee-endpoint.d.ts +91 -0
- package/dist/employee-endpoint.js +141 -0
- package/dist/employee-exec.d.ts +53 -0
- package/dist/employee-exec.js +423 -0
- package/dist/employee-location.d.ts +66 -0
- package/dist/employee-location.js +51 -0
- package/dist/employee-mcp.d.ts +42 -0
- package/dist/employee-mcp.js +181 -0
- package/dist/employee-native.d.ts +18 -0
- package/dist/employee-native.js +38 -0
- package/dist/employee-provider.d.ts +42 -0
- package/dist/employee-provider.js +326 -0
- package/dist/employee-relay.d.ts +18 -0
- package/dist/employee-relay.js +49 -0
- package/dist/employee-shell.d.ts +26 -0
- package/dist/employee-shell.js +89 -0
- package/dist/employee-ssh.d.ts +22 -0
- package/dist/employee-ssh.js +123 -0
- package/dist/employee-tools.d.ts +117 -0
- package/dist/employee-tools.js +292 -0
- package/dist/employee-workspace.d.ts +26 -0
- package/dist/employee-workspace.js +61 -0
- package/dist/index.d.ts +31 -14
- package/dist/index.js +335 -42
- package/dist/input.d.ts +15 -0
- package/dist/input.js +65 -24
- package/dist/local-user-turn.d.ts +24 -0
- package/dist/local-user-turn.js +103 -0
- package/dist/model-catalog.d.ts +55 -0
- package/dist/model-catalog.js +254 -0
- package/dist/model-configuration.d.ts +51 -0
- package/dist/model-configuration.js +212 -0
- package/dist/presentation.d.ts +1 -1
- package/dist/presentation.js +50 -2
- package/dist/protocol-boundary.d.ts +13 -0
- package/dist/protocol-boundary.js +49 -0
- package/dist/provider.d.ts +54 -2
- package/dist/provider.js +92 -29
- package/dist/readiness.js +1 -1
- package/dist/recovery-checkpoint.d.ts +6 -0
- package/dist/recovery-checkpoint.js +71 -0
- package/dist/recovery.d.ts +47 -0
- package/dist/recovery.js +61 -0
- package/dist/remote-details.d.ts +10 -0
- package/dist/remote-details.js +14 -4
- package/dist/rpc-namespace.d.ts +3 -0
- package/dist/rpc-namespace.js +50 -0
- package/dist/stage-timing.d.ts +27 -0
- package/dist/stage-timing.js +39 -0
- package/helpers/ripgrep/LICENSE-vscode +13 -0
- package/helpers/ripgrep/PROVENANCE.md +9 -0
- package/helpers/ripgrep/darwin-arm64/rg +0 -0
- package/helpers/ripgrep/darwin-x64/rg +0 -0
- package/helpers/ripgrep/manifest.json +56 -0
- package/helpers/ripgrep/notices/COPYING-ripgrep +3 -0
- package/helpers/ripgrep/notices/DEPENDENCIES.json +946 -0
- package/helpers/ripgrep/notices/LICENCE-PCRE2.md +103 -0
- package/helpers/ripgrep/notices/LICENSE-MIT-ripgrep +21 -0
- package/helpers/ripgrep/notices/UNLICENSE-ripgrep +24 -0
- package/helpers/ripgrep/notices/aho-corasick-1.1.3/COPYING +3 -0
- package/helpers/ripgrep/notices/aho-corasick-1.1.3/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/aho-corasick-1.1.3/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/anyhow-1.0.100/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/anyhow-1.0.100/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/arbitrary-1.4.2/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/arbitrary-1.4.2/LICENSE-MIT +27 -0
- package/helpers/ripgrep/notices/bstr-1.12.0/COPYING +8 -0
- package/helpers/ripgrep/notices/bstr-1.12.0/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/bstr-1.12.0/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/cc-1.2.41/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/cc-1.2.41/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/cfg-if-1.0.4/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/cfg-if-1.0.4/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/crossbeam-channel-0.5.15/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/crossbeam-channel-0.5.15/LICENSE-MIT +27 -0
- package/helpers/ripgrep/notices/crossbeam-channel-0.5.15/LICENSE-THIRD-PARTY +593 -0
- package/helpers/ripgrep/notices/crossbeam-deque-0.8.6/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/crossbeam-deque-0.8.6/LICENSE-MIT +27 -0
- package/helpers/ripgrep/notices/crossbeam-epoch-0.9.18/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/crossbeam-epoch-0.9.18/LICENSE-MIT +27 -0
- package/helpers/ripgrep/notices/crossbeam-utils-0.8.21/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/crossbeam-utils-0.8.21/LICENSE-MIT +27 -0
- package/helpers/ripgrep/notices/derive_arbitrary-1.4.2/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/derive_arbitrary-1.4.2/LICENSE-MIT +27 -0
- package/helpers/ripgrep/notices/encoding_rs-0.8.35/COPYRIGHT +17 -0
- package/helpers/ripgrep/notices/encoding_rs-0.8.35/LICENSE-APACHE +202 -0
- package/helpers/ripgrep/notices/encoding_rs-0.8.35/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/encoding_rs-0.8.35/LICENSE-WHATWG +26 -0
- package/helpers/ripgrep/notices/encoding_rs_io-0.1.7/COPYING +8 -0
- package/helpers/ripgrep/notices/encoding_rs_io-0.1.7/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/encoding_rs_io-0.1.7/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/find-msvc-tools-0.1.4/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/find-msvc-tools-0.1.4/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/getrandom-0.3.4/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/getrandom-0.3.4/LICENSE-MIT +26 -0
- package/helpers/ripgrep/notices/glob-0.3.3/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/glob-0.3.3/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/itoa-1.0.15/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/itoa-1.0.15/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/jobserver-0.1.34/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/jobserver-0.1.34/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/lexopt-0.3.1/LICENSE +19 -0
- package/helpers/ripgrep/notices/libc-0.2.177/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/libc-0.2.177/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/log-0.4.28/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/log-0.4.28/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/memchr-2.7.6/COPYING +3 -0
- package/helpers/ripgrep/notices/memchr-2.7.6/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/memchr-2.7.6/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/memmap2-0.9.8/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/memmap2-0.9.8/LICENSE-MIT +26 -0
- package/helpers/ripgrep/notices/msvc_spectre_libs-0.1.3/LICENSE +21 -0
- package/helpers/ripgrep/notices/pcre2-0.2.11/COPYING +3 -0
- package/helpers/ripgrep/notices/pcre2-0.2.11/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/pcre2-0.2.11/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/pcre2-sys-0.2.10/COPYING +3 -0
- package/helpers/ripgrep/notices/pcre2-sys-0.2.10/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/pcre2-sys-0.2.10/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/pkg-config-0.3.32/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/pkg-config-0.3.32/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/proc-macro2-1.0.101/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/proc-macro2-1.0.101/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/quote-1.0.41/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/quote-1.0.41/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/r-efi-5.3.0/AUTHORS +74 -0
- package/helpers/ripgrep/notices/regex-1.12.2/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/regex-1.12.2/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/regex-automata-0.4.13/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/regex-automata-0.4.13/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/regex-syntax-0.8.8/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/regex-syntax-0.8.8/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/regex-syntax-0.8.8/src/unicode_tables/LICENSE-UNICODE +57 -0
- package/helpers/ripgrep/notices/ryu-1.0.20/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/ryu-1.0.20/LICENSE-BOOST +23 -0
- package/helpers/ripgrep/notices/same-file-1.0.6/COPYING +3 -0
- package/helpers/ripgrep/notices/same-file-1.0.6/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/same-file-1.0.6/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/serde-1.0.228/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/serde-1.0.228/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/serde_core-1.0.228/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/serde_core-1.0.228/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/serde_derive-1.0.228/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/serde_derive-1.0.228/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/serde_json-1.0.145/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/serde_json-1.0.145/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/shlex-1.3.0/LICENSE-APACHE +13 -0
- package/helpers/ripgrep/notices/shlex-1.3.0/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/syn-2.0.106/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/syn-2.0.106/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/termcolor-1.4.1/COPYING +3 -0
- package/helpers/ripgrep/notices/termcolor-1.4.1/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/termcolor-1.4.1/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/textwrap-0.16.2/LICENSE +21 -0
- package/helpers/ripgrep/notices/tikv-jemalloc-sys-0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/tikv-jemalloc-sys-0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/tikv-jemalloc-sys-0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7/jemalloc/COPYING +27 -0
- package/helpers/ripgrep/notices/tikv-jemallocator-0.6.0/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/tikv-jemallocator-0.6.0/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/unicode-ident-1.0.19/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/unicode-ident-1.0.19/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/unicode-ident-1.0.19/LICENSE-UNICODE +39 -0
- package/helpers/ripgrep/notices/walkdir-2.5.0/COPYING +3 -0
- package/helpers/ripgrep/notices/walkdir-2.5.0/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/walkdir-2.5.0/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/wasip2-1.0.1+wasi-0.2.4/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/wasip2-1.0.1+wasi-0.2.4/LICENSE-Apache-2.0_WITH_LLVM-exception +220 -0
- package/helpers/ripgrep/notices/wasip2-1.0.1+wasi-0.2.4/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/winapi-util-0.1.11/COPYING +3 -0
- package/helpers/ripgrep/notices/winapi-util-0.1.11/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/winapi-util-0.1.11/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/windows-link-0.2.1/license-apache-2.0 +201 -0
- package/helpers/ripgrep/notices/windows-link-0.2.1/license-mit +21 -0
- package/helpers/ripgrep/notices/windows-sys-0.61.2/license-apache-2.0 +201 -0
- package/helpers/ripgrep/notices/windows-sys-0.61.2/license-mit +21 -0
- package/helpers/ripgrep/notices/wit-bindgen-0.46.0/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/wit-bindgen-0.46.0/LICENSE-Apache-2.0_WITH_LLVM-exception +220 -0
- package/helpers/ripgrep/notices/wit-bindgen-0.46.0/LICENSE-MIT +23 -0
- package/locale/en.json +1 -1
- package/locale/zh.json +2 -2
- package/package.json +30 -12
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { NeedError } from "./remote.js";
|
|
2
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
3
|
+
const fail = (code) => new NeedError(code, '员工电脑的工作区或执行连接已变化,本轮没有继续执行。', '当前工作区绑定与员工端执行器');
|
|
4
|
+
const absolute = (s) => typeof s === 'string' && s.startsWith('/') && !s.includes('\0');
|
|
5
|
+
const uuid = (s) => typeof s === 'string' && /^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i.test(s);
|
|
6
|
+
const object = (x) => !!x && typeof x === 'object' && !Array.isArray(x);
|
|
7
|
+
const same = isDeepStrictEqual;
|
|
8
|
+
function endpointValue(value, local, settingsPath) {
|
|
9
|
+
if (!object(value) || value.version !== 1 || !same(value.local, local) || !object(value.endpoint) || !object(value.bridge))
|
|
10
|
+
throw fail('EMPLOYEE_ENDPOINT_INVALID');
|
|
11
|
+
const e = value.endpoint, b = value.bridge;
|
|
12
|
+
if (typeof e.deviceId !== 'string' || !/^[a-f0-9]{64}$/.test(e.deviceId) || !uuid(e.endpointInstanceId) || !uuid(e.executionContextId) || typeof e.remoteUser !== 'string' || !e.remoteUser || !Number.isInteger(e.remoteUid) || Number(e.remoteUid) < 1 || Number(e.remoteUid) > 4294967294 || !absolute(e.remoteHome) || e.endpointCwd !== e.remoteHome || !absolute(e.endpointShell) || b.protocolVersion !== 1 || b.command !== '/usr/local/aw-host/bin/aw-employee-bridge' || b.settingsPath !== settingsPath || !uuid(b.bridgeInstanceId) || !Number.isSafeInteger(b.generation) || Number(b.generation) < 1)
|
|
13
|
+
throw fail('EMPLOYEE_ENDPOINT_INVALID');
|
|
14
|
+
// Copy only the public target fields; never retain process/environment snapshots.
|
|
15
|
+
return { version: 1, local: structuredClone(local), endpoint: { deviceId: e.deviceId, endpointInstanceId: e.endpointInstanceId, executionContextId: e.executionContextId, remoteUser: e.remoteUser, remoteUid: Number(e.remoteUid), remoteHome: e.remoteHome, endpointCwd: e.endpointCwd, endpointShell: e.endpointShell }, bridge: { protocolVersion: 1, command: b.command, settingsPath, bridgeInstanceId: b.bridgeInstanceId, generation: Number(b.generation) } };
|
|
16
|
+
}
|
|
17
|
+
/** One admission binds source/canonical directory and real N/S execution identity. No host-path substitute. */
|
|
18
|
+
export async function admitEmployeeLocation(options) {
|
|
19
|
+
const { api, network, workspace, connection, settingsPath } = options;
|
|
20
|
+
const read = async (signal) => {
|
|
21
|
+
signal?.throwIfAborted();
|
|
22
|
+
const state = api.getWorkspaceLocationState();
|
|
23
|
+
if (state.phase !== 'ready' || state.instanceId !== connection.instanceId || state.epoch !== connection.epoch)
|
|
24
|
+
throw fail('EMPLOYEE_CONTEXT_CHANGED');
|
|
25
|
+
const rows = state.items.filter(x => x.workspaceId === workspace.id), item = rows[0];
|
|
26
|
+
if (rows.length !== 1 || !item || item.source !== 'local' || item.status !== 'selected' || !item.bindingId)
|
|
27
|
+
throw fail('EMPLOYEE_CONTEXT_CHANGED');
|
|
28
|
+
const request = { instanceId: connection.instanceId, epoch: connection.epoch, workspaceId: workspace.id, expectedBindingId: item.bindingId };
|
|
29
|
+
const result = await api.getLocalWorkspaceContext(request);
|
|
30
|
+
signal?.throwIfAborted();
|
|
31
|
+
if (!result.ok)
|
|
32
|
+
throw new NeedError(result.error.code, result.error.message, result.error.requires);
|
|
33
|
+
const c = result.value;
|
|
34
|
+
if (c.version !== 1 || c.source !== 'local' || c.instanceId !== connection.instanceId || c.epoch !== connection.epoch || c.workspaceId !== workspace.id || c.bindingId !== item.bindingId || !absolute(c.directory?.path) || c.directory.path !== workspace.path || !Number.isSafeInteger(c.directory.device) || !Number.isSafeInteger(c.directory.inode))
|
|
35
|
+
throw fail('EMPLOYEE_CONTEXT_CHANGED');
|
|
36
|
+
const after = api.getWorkspaceLocationState();
|
|
37
|
+
if (after.instanceId !== connection.instanceId || after.epoch !== connection.epoch || after.phase !== 'ready' || !after.items.some(x => x.workspaceId === workspace.id && x.bindingId === item.bindingId && x.source === 'local' && x.status === 'selected'))
|
|
38
|
+
throw fail('EMPLOYEE_CONTEXT_CHANGED');
|
|
39
|
+
return { request, local: { instanceId: c.instanceId, epoch: c.epoch, workspaceId: c.workspaceId, bindingId: c.bindingId, directory: structuredClone(c.directory) } };
|
|
40
|
+
};
|
|
41
|
+
const initial = await read(options.signal);
|
|
42
|
+
if (options.expectedLocal && !same(initial.local, options.expectedLocal))
|
|
43
|
+
throw fail('EMPLOYEE_CONTEXT_CHANGED');
|
|
44
|
+
const prepare = async (signal) => { signal.throwIfAborted(); const before = await read(signal); if (!same(before, initial))
|
|
45
|
+
throw fail('EMPLOYEE_CONTEXT_CHANGED'); const result = await network.prepareEmployeeEndpoint(initial.request, signal); signal.throwIfAborted(); if (!result.ok)
|
|
46
|
+
throw new NeedError(result.error.code, result.error.message, result.error.needs); const fresh = endpointValue(result.value, initial.local, settingsPath); const after = await read(signal); if (!same(after, initial))
|
|
47
|
+
throw fail('EMPLOYEE_CONTEXT_CHANGED'); return fresh; };
|
|
48
|
+
const endpoint = await prepare(options.signal ?? new AbortController().signal);
|
|
49
|
+
return { cwd: initial.local.directory.path, bindingId: initial.local.bindingId, workspaceId: initial.local.workspaceId, endpoint, validate: async (signal) => { const fresh = await prepare(signal); if (!same(fresh, endpoint))
|
|
50
|
+
throw fail('EMPLOYEE_CONTEXT_CHANGED'); } };
|
|
51
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type Socket } from 'node:net';
|
|
2
|
+
import { type JSONRPCMessage, type Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
4
|
+
import type { EmployeeLease } from './employee-endpoint.ts';
|
|
5
|
+
import { employeeRouting } from './employee-relay.ts';
|
|
6
|
+
import type { DetailObservation } from './conversation-details.ts';
|
|
7
|
+
import type { EmployeeShell } from './employee-shell.ts';
|
|
8
|
+
export declare const EMPLOYEE_MCP_TOOLS: Tool[];
|
|
9
|
+
/** Same newline-delimited MCP messages as stdio, over an employee-only Unix stream. */
|
|
10
|
+
export declare class EmployeeSocketTransport implements Transport {
|
|
11
|
+
private socket;
|
|
12
|
+
private maxBytes;
|
|
13
|
+
onclose?: () => void;
|
|
14
|
+
onerror?: (error: Error) => void;
|
|
15
|
+
onmessage?: (message: JSONRPCMessage) => void;
|
|
16
|
+
private pending;
|
|
17
|
+
private ended;
|
|
18
|
+
private failure?;
|
|
19
|
+
get error(): Error | undefined;
|
|
20
|
+
private readonly lifetime;
|
|
21
|
+
readonly signal: AbortSignal;
|
|
22
|
+
constructor(socket: Socket, maxBytes: number);
|
|
23
|
+
private failed;
|
|
24
|
+
start(): Promise<void>;
|
|
25
|
+
send(message: JSONRPCMessage): Promise<void>;
|
|
26
|
+
close(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export interface EmployeeMcpOptions {
|
|
29
|
+
lease: EmployeeLease;
|
|
30
|
+
maxBytes: number;
|
|
31
|
+
killGraceMs: number;
|
|
32
|
+
activeSignal: () => AbortSignal | undefined;
|
|
33
|
+
detail: (event: DetailObservation) => void;
|
|
34
|
+
captureDetail?: () => ((event: DetailObservation) => void);
|
|
35
|
+
runShell?: EmployeeShell;
|
|
36
|
+
}
|
|
37
|
+
export interface EmployeeMcp {
|
|
38
|
+
routing: ReturnType<typeof employeeRouting>;
|
|
39
|
+
signal: AbortSignal;
|
|
40
|
+
close(): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
export declare function openEmployeeMcp(options: EmployeeMcpOptions): Promise<EmployeeMcp>;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { createServer } from 'node:net';
|
|
2
|
+
import { mkdtemp, lstat, rm, access } from 'node:fs/promises';
|
|
3
|
+
import { constants } from 'node:fs';
|
|
4
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
5
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, JSONRPCMessageSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
+
import { createEmployeeTools } from "./employee-tools.js";
|
|
7
|
+
import { employeeRouting } from "./employee-relay.js";
|
|
8
|
+
import { NeedError } from "./remote.js";
|
|
9
|
+
const string = { type: 'string' }, integer = { type: 'integer' }, boolean = { type: 'boolean' };
|
|
10
|
+
function schema(name, properties, required) { return { name, description: '在员工电脑所选工作区执行' + name + ';不在主机回退。', inputSchema: { type: 'object', properties, required, additionalProperties: false } }; }
|
|
11
|
+
export const EMPLOYEE_MCP_TOOLS = [
|
|
12
|
+
schema('Read', { file_path: string, offset: integer, limit: integer, pages: string }, ['file_path']),
|
|
13
|
+
schema('Write', { file_path: string, content: string }, ['file_path', 'content']),
|
|
14
|
+
schema('Edit', { file_path: string, old_string: string, new_string: string, replace_all: boolean }, ['file_path', 'old_string', 'new_string']),
|
|
15
|
+
schema('Glob', { pattern: string, path: string }, ['pattern']),
|
|
16
|
+
schema('Grep', { pattern: string, path: string, glob: string, output_mode: string, '-B': integer, '-A': integer, '-C': integer, '-n': boolean, '-i': boolean, type: string, head_limit: integer, offset: integer, multiline: boolean }, ['pattern']),
|
|
17
|
+
schema('Bash', { command: string, timeout: integer, description: string, run_in_background: boolean, dangerouslyDisableSandbox: boolean }, ['command']),
|
|
18
|
+
];
|
|
19
|
+
/** Same newline-delimited MCP messages as stdio, over an employee-only Unix stream. */
|
|
20
|
+
export class EmployeeSocketTransport {
|
|
21
|
+
socket;
|
|
22
|
+
maxBytes;
|
|
23
|
+
onclose;
|
|
24
|
+
onerror;
|
|
25
|
+
onmessage;
|
|
26
|
+
pending = Buffer.alloc(0);
|
|
27
|
+
ended = false;
|
|
28
|
+
failure;
|
|
29
|
+
get error() { return this.failure; }
|
|
30
|
+
lifetime = new AbortController();
|
|
31
|
+
signal = this.lifetime.signal;
|
|
32
|
+
constructor(socket, maxBytes) {
|
|
33
|
+
this.socket = socket;
|
|
34
|
+
this.maxBytes = maxBytes;
|
|
35
|
+
}
|
|
36
|
+
failed(error) { try {
|
|
37
|
+
this.onerror?.(error);
|
|
38
|
+
}
|
|
39
|
+
catch (observation) {
|
|
40
|
+
this.failure = observation instanceof Error ? observation : new Error('EMPLOYEE_MCP_OBSERVATION_FAILED');
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
void this.close();
|
|
44
|
+
} }
|
|
45
|
+
async start() {
|
|
46
|
+
this.socket.on('error', error => this.failed(error));
|
|
47
|
+
this.socket.on('close', () => void this.close());
|
|
48
|
+
this.socket.on('data', (bytes) => {
|
|
49
|
+
try {
|
|
50
|
+
let cursor = 0;
|
|
51
|
+
while (cursor < bytes.length) {
|
|
52
|
+
const newline = bytes.indexOf(10, cursor), end = newline < 0 ? bytes.length : newline;
|
|
53
|
+
if (this.pending.length + end - cursor > this.maxBytes)
|
|
54
|
+
throw new NeedError('EMPLOYEE_MCP_MESSAGE_TOO_LARGE', '本机工具消息超过acpMaxMessageBytes,未截断。', '调整具名会话消息容量');
|
|
55
|
+
this.pending = Buffer.concat([this.pending, bytes.subarray(cursor, end)]);
|
|
56
|
+
cursor = newline < 0 ? bytes.length : newline + 1;
|
|
57
|
+
if (newline >= 0) {
|
|
58
|
+
const line = new TextDecoder('utf8', { fatal: true }).decode(this.pending);
|
|
59
|
+
this.pending = Buffer.alloc(0);
|
|
60
|
+
this.onmessage?.(JSONRPCMessageSchema.parse(JSON.parse(line)));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
this.failed(error);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async send(message) {
|
|
70
|
+
if (this.ended)
|
|
71
|
+
throw new Error('EMPLOYEE_MCP_CLOSED');
|
|
72
|
+
const wire = Buffer.from(JSON.stringify(message) + '\n');
|
|
73
|
+
if (wire.length - 1 > this.maxBytes) {
|
|
74
|
+
await this.close();
|
|
75
|
+
throw new Error('EMPLOYEE_MCP_MESSAGE_TOO_LARGE');
|
|
76
|
+
}
|
|
77
|
+
await new Promise((yes, no) => this.socket.write(wire, error => error ? no(error) : yes()));
|
|
78
|
+
}
|
|
79
|
+
async close() { if (this.ended)
|
|
80
|
+
return; this.ended = true; this.lifetime.abort(); this.socket.destroy(); this.onclose?.(); }
|
|
81
|
+
}
|
|
82
|
+
export async function openEmployeeMcp(options) {
|
|
83
|
+
if (process.platform !== 'darwin')
|
|
84
|
+
throw new NeedError('EMPLOYEE_PLATFORM_UNSUPPORTED', '本机执行目前只支持Mac。', '已交Mac员工端合同');
|
|
85
|
+
if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 1)
|
|
86
|
+
throw new NeedError('SETTINGS_REQUIRED', '本机工具消息容量无效。', 'acpMaxMessageBytes');
|
|
87
|
+
await access('/usr/bin/nc', constants.X_OK);
|
|
88
|
+
const tools = createEmployeeTools({ cwd: options.lease.target.local.directory.path, shell: options.lease.target.endpoint.endpointShell, uid: options.lease.target.endpoint.remoteUid, maxBytes: options.maxBytes, killGraceMs: options.killGraceMs, detail: observe, runShell: options.runShell, validate: signal => options.lease.validate(signal).then(() => { }) });
|
|
89
|
+
const root = await mkdtemp('/private/tmp/aw-x87-'), socketPath = root + '/mcp';
|
|
90
|
+
try {
|
|
91
|
+
const info = await lstat(root);
|
|
92
|
+
if ((info.mode & 0o077) !== 0 || info.uid !== process.getuid?.())
|
|
93
|
+
throw new NeedError('EMPLOYEE_MCP_STORAGE_INVALID', '本机工具私有通道不可用。', '当前员工的700私有Unix目录');
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
await tools.close();
|
|
97
|
+
await rm(root, { recursive: true });
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
const clients = new Set(), transports = new Set();
|
|
101
|
+
let closing, diagnosticFailure;
|
|
102
|
+
const failureLifetime = new AbortController();
|
|
103
|
+
const poison = (error) => {
|
|
104
|
+
diagnosticFailure ??= error;
|
|
105
|
+
failureLifetime.abort(error);
|
|
106
|
+
// Close is memoized synchronously: no fresh connection/call can pass the fence.
|
|
107
|
+
void options.lease.close().catch(failure => { diagnosticFailure ??= failure; });
|
|
108
|
+
void close().catch(failure => { diagnosticFailure ??= failure; });
|
|
109
|
+
};
|
|
110
|
+
function observeUsing(event, sink) { if (diagnosticFailure)
|
|
111
|
+
throw diagnosticFailure; try {
|
|
112
|
+
sink(event);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
poison(error);
|
|
116
|
+
throw error;
|
|
117
|
+
} }
|
|
118
|
+
function observe(event) { observeUsing(event, options.detail); }
|
|
119
|
+
const server = createServer(socket => {
|
|
120
|
+
if (closing || failureLifetime.signal.aborted || options.lease.signal.aborted) {
|
|
121
|
+
socket.destroy();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const mcp = new Server({ name: 'ai-workbench-employee', version: '0.8.10' }, { capabilities: { tools: {} } }), transport = new EmployeeSocketTransport(socket, options.maxBytes);
|
|
125
|
+
clients.add(mcp);
|
|
126
|
+
transports.add(transport);
|
|
127
|
+
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: EMPLOYEE_MCP_TOOLS }));
|
|
128
|
+
mcp.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
129
|
+
const active = options.activeSignal();
|
|
130
|
+
if (!active || active.aborted)
|
|
131
|
+
return { isError: true, content: [{ type: 'text', text: 'EMPLOYEE_NO_ACTIVE_TURN:当前没有已接纳的用户轮次,本机工具未执行。' }] };
|
|
132
|
+
const signal = AbortSignal.any([active, extra.signal, transport.signal, options.lease.signal, failureLifetime.signal]);
|
|
133
|
+
const sink = options.captureDetail?.() ?? options.detail;
|
|
134
|
+
return tools.call(request.params.name, request.params.arguments ?? {}, signal, event => observeUsing(event, sink));
|
|
135
|
+
});
|
|
136
|
+
mcp.onerror = () => { try {
|
|
137
|
+
observe({ kind: 'session', severity: 'warning', code: 'CLAUDE_EMPLOYEE_MCP_UNKNOWN', summary: '本机工具连接出现协议或传输错误;在途结果未知,没有重放' });
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
poison(error);
|
|
141
|
+
} };
|
|
142
|
+
mcp.onclose = () => { clients.delete(mcp); transports.delete(transport); };
|
|
143
|
+
void mcp.connect(transport).catch(() => { socket.destroy(); });
|
|
144
|
+
});
|
|
145
|
+
const close = () => closing ??= Promise.resolve().then(async () => {
|
|
146
|
+
options.lease.signal.removeEventListener('abort', aborted);
|
|
147
|
+
const errors = [], clean = async (operation) => { try {
|
|
148
|
+
await operation();
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
if (!errors.includes(error))
|
|
152
|
+
errors.push(error);
|
|
153
|
+
} };
|
|
154
|
+
for (const transport of transports)
|
|
155
|
+
await clean(() => transport.close());
|
|
156
|
+
await Promise.all([...clients].map(client => clean(() => client.close())));
|
|
157
|
+
await clean(() => tools.close());
|
|
158
|
+
await clean(() => options.lease.close());
|
|
159
|
+
if (server.listening)
|
|
160
|
+
await clean(() => new Promise((yes, no) => server.close(error => error ? no(error) : yes())));
|
|
161
|
+
await clean(() => rm(root, { recursive: true }));
|
|
162
|
+
if (diagnosticFailure && !errors.includes(diagnosticFailure))
|
|
163
|
+
errors.unshift(diagnosticFailure);
|
|
164
|
+
if (errors.length === 1)
|
|
165
|
+
throw errors[0];
|
|
166
|
+
if (errors.length)
|
|
167
|
+
throw new AggregateError(errors, 'EMPLOYEE_MCP_CLEANUP_FAILED');
|
|
168
|
+
});
|
|
169
|
+
const aborted = () => { void close().catch(error => { diagnosticFailure ??= error; failureLifetime.abort(error); }); };
|
|
170
|
+
try {
|
|
171
|
+
await new Promise((yes, no) => { server.once('error', no); server.listen(socketPath, () => { server.removeListener('error', no); yes(); }); });
|
|
172
|
+
options.lease.signal.addEventListener('abort', aborted, { once: true });
|
|
173
|
+
if (options.lease.signal.aborted)
|
|
174
|
+
throw options.lease.signal.reason;
|
|
175
|
+
return { routing: employeeRouting(options.lease.target, socketPath, options.maxBytes), signal: failureLifetime.signal, close };
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
await close();
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Fixed native input already proven by D18/D20; never resolve a PATH executable. */
|
|
2
|
+
export declare const EMPLOYEE_NATIVE: Readonly<{
|
|
3
|
+
version: "0.160.1";
|
|
4
|
+
platform: "darwin";
|
|
5
|
+
arch: "arm64";
|
|
6
|
+
package: "@openai/codex-darwin-arm64";
|
|
7
|
+
packageVersion: "0.160.1-darwin-arm64";
|
|
8
|
+
triple: "aarch64-apple-darwin";
|
|
9
|
+
sha256: "09fa44fdc37a5fc70dc1ace31235f90468a2e193d0e85f7552eab068ea2582be";
|
|
10
|
+
}>;
|
|
11
|
+
export declare function resolveEmployeeNative(ports?: {
|
|
12
|
+
platform?: string;
|
|
13
|
+
arch?: string;
|
|
14
|
+
resolve?: (name: string) => string;
|
|
15
|
+
}): Promise<{
|
|
16
|
+
path: string;
|
|
17
|
+
sha256: string;
|
|
18
|
+
}>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { readFile, lstat, realpath, access } from 'node:fs/promises';
|
|
3
|
+
import { constants } from 'node:fs';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
import { NeedError } from "./remote.js";
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
/** Fixed native input already proven by D18/D20; never resolve a PATH executable. */
|
|
9
|
+
export const EMPLOYEE_NATIVE = Object.freeze({ version: '0.160.1', platform: 'darwin', arch: 'arm64', package: '@openai/codex-darwin-arm64', packageVersion: '0.160.1-darwin-arm64', triple: 'aarch64-apple-darwin', sha256: '09fa44fdc37a5fc70dc1ace31235f90468a2e193d0e85f7552eab068ea2582be' });
|
|
10
|
+
export async function resolveEmployeeNative(ports = {}) {
|
|
11
|
+
if ((ports.platform ?? process.platform) !== EMPLOYEE_NATIVE.platform || (ports.arch ?? process.arch) !== EMPLOYEE_NATIVE.arch)
|
|
12
|
+
throw new NeedError('EMPLOYEE_PLATFORM_UNSUPPORTED', '当前版本的Codex本机执行仅验证了Apple芯片Mac,本轮没有执行。', '对应平台固定执行器及验证');
|
|
13
|
+
let manifest;
|
|
14
|
+
try {
|
|
15
|
+
manifest = (ports.resolve ?? require.resolve)(EMPLOYEE_NATIVE.package + '/package.json');
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
throw new NeedError('AW_EMPLOYEE_BINARY_MISSING', 'Codex本机执行组件没有装齐,本轮没有执行,请联系管理员。', '随插件安装的固定Codex0.160.1执行组件');
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const metadata = JSON.parse(await readFile(manifest, 'utf8'));
|
|
22
|
+
if (metadata.version !== EMPLOYEE_NATIVE.packageVersion)
|
|
23
|
+
throw new NeedError('AW_EMPLOYEE_BINARY_VERSION', 'Codex本机执行组件版本不匹配,本轮没有执行。', '固定Codex0.160.1组件');
|
|
24
|
+
const path = join(dirname(manifest), 'vendor', EMPLOYEE_NATIVE.triple, 'bin', 'codex');
|
|
25
|
+
const info = await lstat(path);
|
|
26
|
+
if (!info.isFile() || info.isSymbolicLink() || await realpath(path) !== path)
|
|
27
|
+
throw new NeedError('AW_EMPLOYEE_BINARY_INVALID', 'Codex本机执行组件位置无效,本轮没有执行。', '安装包内的固定原生执行器');
|
|
28
|
+
await access(path, constants.X_OK);
|
|
29
|
+
if (createHash('sha256').update(await readFile(path)).digest('hex') !== EMPLOYEE_NATIVE.sha256)
|
|
30
|
+
throw new NeedError('AW_EMPLOYEE_BINARY_HASH', 'Codex本机执行组件校验不一致,本轮没有执行。', '固定原生执行器SHA256');
|
|
31
|
+
return { path, sha256: EMPLOYEE_NATIVE.sha256 };
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error instanceof NeedError)
|
|
35
|
+
throw error;
|
|
36
|
+
throw new NeedError('AW_EMPLOYEE_BINARY_INVALID', 'Codex本机执行组件无法核验,本轮没有执行。', '完整安装包与固定执行器');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { NetworkApi } from '@ai-workbench/network/api';
|
|
2
|
+
import type { AgentDefinition } from './agents.ts';
|
|
3
|
+
import type { ConnectApi, ConnectTarget, ConnectionState, Project, Runtime, RuntimeRequest } from './provider.ts';
|
|
4
|
+
import { type BrokerIo, type SshArgv } from './broker.ts';
|
|
5
|
+
import type { DetailChannel } from './remote-details.ts';
|
|
6
|
+
import type { LocalUserTurnAcceptance } from '@ai-workbench/connect/api';
|
|
7
|
+
export interface EmployeeProviderOptions {
|
|
8
|
+
currentUserTurn(request: RuntimeRequest): {
|
|
9
|
+
turn: number;
|
|
10
|
+
userMessageId: string;
|
|
11
|
+
};
|
|
12
|
+
getNetwork(): NetworkApi | undefined;
|
|
13
|
+
workspace(request: RuntimeRequest): Promise<{
|
|
14
|
+
id: string;
|
|
15
|
+
path: string;
|
|
16
|
+
}>;
|
|
17
|
+
native(): Promise<{
|
|
18
|
+
path: string;
|
|
19
|
+
sha256: string;
|
|
20
|
+
}>;
|
|
21
|
+
argv?: SshArgv;
|
|
22
|
+
io?: BrokerIo;
|
|
23
|
+
}
|
|
24
|
+
interface Options {
|
|
25
|
+
employee?: EmployeeProviderOptions;
|
|
26
|
+
open?: (target: ConnectTarget, project: Project, request: RuntimeRequest) => Promise<Runtime>;
|
|
27
|
+
details?: (request: RuntimeRequest, api: ConnectApi, connection: ConnectionState, target: ConnectTarget) => Promise<DetailChannel>;
|
|
28
|
+
}
|
|
29
|
+
interface Admission {
|
|
30
|
+
api: ConnectApi;
|
|
31
|
+
target: ConnectTarget;
|
|
32
|
+
connection: ConnectionState;
|
|
33
|
+
request: RuntimeRequest;
|
|
34
|
+
stable: (signal?: AbortSignal) => Promise<unknown>;
|
|
35
|
+
}
|
|
36
|
+
/** Read-only local classification and explicit acceptance precede even ensureReady's SSH status. */
|
|
37
|
+
export declare function acceptEmployeeUserTurn(get: () => ConnectApi | undefined, employee: EmployeeProviderOptions, agent: AgentDefinition, api: ConnectApi, request: RuntimeRequest): Promise<Readonly<LocalUserTurnAcceptance> | undefined>;
|
|
38
|
+
/** Recheck the admitted read-only facts after readiness awaits, before the catalog command. */
|
|
39
|
+
export declare function validateEmployeeUserTurn(get: () => ConnectApi | undefined, employee: EmployeeProviderOptions, api: ConnectApi, request: RuntimeRequest, connection: ConnectionState): Promise<void>;
|
|
40
|
+
/** Source admission is separate from L's legacy host project resolver. */
|
|
41
|
+
export declare function createEmployeeProvider(get: () => ConnectApi | undefined, options: Options, agent: AgentDefinition): (input: Admission) => Promise<Runtime | undefined>;
|
|
42
|
+
export {};
|