@ai-workbench/remote-agent 0.8.5 → 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 +33 -8
- package/dist/acp.d.ts +22 -2
- package/dist/acp.js +143 -25
- package/dist/approval-label.d.ts +1 -1
- package/dist/approval-label.js +15 -3
- package/dist/broker-runtime.js +40 -8
- package/dist/broker.js +5 -1
- 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 +0 -1
- package/dist/conversation-details.js +37 -7
- 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 +6 -0
- package/dist/index.js +148 -28
- package/dist/input.d.ts +11 -0
- package/dist/input.js +61 -23
- package/dist/local-user-turn.d.ts +24 -0
- package/dist/local-user-turn.js +103 -0
- package/dist/presentation.d.ts +1 -1
- package/dist/presentation.js +46 -2
- package/dist/provider.d.ts +36 -2
- package/dist/provider.js +33 -0
- package/dist/readiness.js +1 -1
- package/dist/recovery.d.ts +14 -1
- package/dist/recovery.js +11 -2
- package/dist/remote-details.d.ts +7 -0
- package/dist/remote-details.js +11 -1
- 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 +29 -11
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { ConnectApi as LApi, LocalWorkspaceContextRequest } from '@ai-workbench/connect/api';
|
|
2
|
+
import type { ConnectTarget } from './provider.ts';
|
|
3
|
+
import type { DetailObservation } from './conversation-details.ts';
|
|
4
|
+
/** Exact consumer subset of N0.4.4 employee-endpoint v1; no new provider fields. */
|
|
5
|
+
export interface EmployeeEndpointTarget {
|
|
6
|
+
version: 1;
|
|
7
|
+
local: {
|
|
8
|
+
instanceId: string;
|
|
9
|
+
epoch: number;
|
|
10
|
+
workspaceId: string;
|
|
11
|
+
bindingId: string;
|
|
12
|
+
directory: {
|
|
13
|
+
path: string;
|
|
14
|
+
device: number;
|
|
15
|
+
inode: number;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
endpoint: {
|
|
19
|
+
deviceId: string;
|
|
20
|
+
endpointInstanceId: string;
|
|
21
|
+
executionContextId: string;
|
|
22
|
+
remoteUser: string;
|
|
23
|
+
remoteUid: number;
|
|
24
|
+
remoteHome: string;
|
|
25
|
+
endpointCwd: string;
|
|
26
|
+
endpointShell: string;
|
|
27
|
+
};
|
|
28
|
+
bridge: {
|
|
29
|
+
protocolVersion: 1;
|
|
30
|
+
command: '/usr/local/aw-host/bin/aw-employee-bridge';
|
|
31
|
+
settingsPath: string;
|
|
32
|
+
bridgeInstanceId: string;
|
|
33
|
+
generation: number;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export interface EmployeeNetworkApi {
|
|
37
|
+
prepareEmployeeEndpoint(request: LocalWorkspaceContextRequest, signal: AbortSignal): Promise<{
|
|
38
|
+
ok: true;
|
|
39
|
+
value: EmployeeEndpointTarget;
|
|
40
|
+
} | {
|
|
41
|
+
ok: false;
|
|
42
|
+
error: {
|
|
43
|
+
code: string;
|
|
44
|
+
needs: string;
|
|
45
|
+
message: string;
|
|
46
|
+
actor: string;
|
|
47
|
+
};
|
|
48
|
+
}>;
|
|
49
|
+
getState(): {
|
|
50
|
+
phase: 'starting' | 'online' | 'blocked' | 'stopped';
|
|
51
|
+
};
|
|
52
|
+
onState(listener: (event: {
|
|
53
|
+
kind: 'lost' | 'restored';
|
|
54
|
+
state: {
|
|
55
|
+
phase: 'starting' | 'online' | 'blocked' | 'stopped';
|
|
56
|
+
};
|
|
57
|
+
}) => void): () => void;
|
|
58
|
+
}
|
|
59
|
+
export type EmployeeConnectApi = Pick<LApi, 'getContractInfo' | 'getConnectionState' | 'onConnectionState' | 'getSshTarget' | 'getLocalWorkspaceContext' | 'getWorkspaceLocationState' | 'onWorkspaceLocationState'>;
|
|
60
|
+
export interface EmployeeWorkspace {
|
|
61
|
+
workspaceId: string;
|
|
62
|
+
directory: {
|
|
63
|
+
path: string;
|
|
64
|
+
device: number;
|
|
65
|
+
inode: number;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export interface EmployeeLeaseOptions {
|
|
69
|
+
getConnect: () => EmployeeConnectApi | undefined;
|
|
70
|
+
getNetwork: () => EmployeeNetworkApi | undefined;
|
|
71
|
+
readWorkspace: () => Promise<EmployeeWorkspace>;
|
|
72
|
+
host: ConnectTarget;
|
|
73
|
+
sessionId: string;
|
|
74
|
+
control: (host: ConnectTarget, command: string, signal?: AbortSignal) => Promise<string>;
|
|
75
|
+
detail: (detail: DetailObservation) => void;
|
|
76
|
+
}
|
|
77
|
+
export declare function endpointArguments(target: EmployeeEndpointTarget): string[];
|
|
78
|
+
export declare function endpointCommand(target: EmployeeEndpointTarget): string;
|
|
79
|
+
export declare function verifyEmployeeTarget(target: EmployeeEndpointTarget, request: LocalWorkspaceContextRequest, directory: EmployeeWorkspace['directory'], host: ConnectTarget): void;
|
|
80
|
+
export declare function verifyBridgeTarget(value: unknown, expected: EmployeeEndpointTarget): {
|
|
81
|
+
sshArgs: string[];
|
|
82
|
+
};
|
|
83
|
+
/** Holds only this workspace's dispatch fence. N continues to own the shared device service. */
|
|
84
|
+
export interface EmployeeLease {
|
|
85
|
+
target: EmployeeEndpointTarget;
|
|
86
|
+
request: Readonly<LocalWorkspaceContextRequest>;
|
|
87
|
+
signal: AbortSignal;
|
|
88
|
+
validate(callerSignal?: AbortSignal): Promise<EmployeeEndpointTarget>;
|
|
89
|
+
close(): Promise<void>;
|
|
90
|
+
}
|
|
91
|
+
export declare function openEmployeeLease(options: EmployeeLeaseOptions, initialSignal?: AbortSignal): Promise<EmployeeLease>;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { lstat, realpath } from 'node:fs/promises';
|
|
2
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
3
|
+
import { sameTarget, unwrap } from "./provider.js";
|
|
4
|
+
import { NeedError, shellQuote } from "./remote.js";
|
|
5
|
+
const fail = (code = 'EMPLOYEE_CONTEXT_CHANGED') => new NeedError(code, '本机工作区或连接已改变,原工具已停止;没有改在主机执行。', '当前L本地绑定与N/S同代员工端点');
|
|
6
|
+
const uuid = (value) => typeof value === 'string' && /^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/.test(value);
|
|
7
|
+
function clean(value) { return typeof value === 'string' && !!value && !/[\x00-\x1f\x7f]/.test(value); }
|
|
8
|
+
export function endpointArguments(target) {
|
|
9
|
+
return ['--settings', target.bridge.settingsPath, '--device', target.endpoint.deviceId, '--if-instance', target.bridge.bridgeInstanceId, '--if-generation', String(target.bridge.generation), '--if-endpoint', target.endpoint.endpointInstanceId, '--if-context', target.endpoint.executionContextId];
|
|
10
|
+
}
|
|
11
|
+
export function endpointCommand(target) { return [target.bridge.command, 'target', ...endpointArguments(target)].map(shellQuote).join(' '); }
|
|
12
|
+
export function verifyEmployeeTarget(target, request, directory, host) {
|
|
13
|
+
if (target?.version !== 1 || !target.local || !target.endpoint || !target.bridge || target.local.instanceId !== request.instanceId || target.local.epoch !== request.epoch || target.local.workspaceId !== request.workspaceId || target.local.bindingId !== request.expectedBindingId || !isDeepStrictEqual(target.local.directory, directory))
|
|
14
|
+
throw fail();
|
|
15
|
+
const e = target.endpoint, b = target.bridge;
|
|
16
|
+
if (!/^[a-f0-9]{64}$/.test(e.deviceId) || ![e.endpointInstanceId, e.executionContextId, b.bridgeInstanceId].every(uuid) || !Number.isSafeInteger(b.generation) || b.generation < 1 || b.protocolVersion !== 1 || b.command !== '/usr/local/aw-host/bin/aw-employee-bridge' || b.settingsPath !== host.session.settingsPath || ![e.remoteUser, e.remoteHome, e.endpointCwd, e.endpointShell].every(clean) || !e.remoteHome.startsWith('/') || !e.endpointCwd.startsWith('/') || !e.endpointShell.startsWith('/') || e.remoteUid !== process.getuid?.() || process.getuid?.() !== process.geteuid?.() || e.remoteUid < 1)
|
|
17
|
+
throw fail('EMPLOYEE_TARGET_INVALID');
|
|
18
|
+
}
|
|
19
|
+
export function verifyBridgeTarget(value, expected) {
|
|
20
|
+
const raw = value;
|
|
21
|
+
if (!raw || raw.type !== 'target' || raw.protocolVersion !== 1 || !raw.endpoint || raw.endpoint.state !== 'online' || !Array.isArray(raw.sshArgs) || !raw.sshArgs.length || raw.sshArgs.some(x => typeof x !== 'string' || x.includes('\0')))
|
|
22
|
+
throw fail('EMPLOYEE_BRIDGE_PROTOCOL_INVALID');
|
|
23
|
+
const endpoint = raw.endpoint;
|
|
24
|
+
for (const [field, value] of Object.entries({ ...expected.endpoint, bridgeInstanceId: expected.bridge.bridgeInstanceId, generation: expected.bridge.generation })) {
|
|
25
|
+
// N's shell/cwd are confirmed by ready; all public identity and execution facts must match.
|
|
26
|
+
if (endpoint[field] !== value)
|
|
27
|
+
throw fail();
|
|
28
|
+
}
|
|
29
|
+
return { sshArgs: raw.sshArgs };
|
|
30
|
+
}
|
|
31
|
+
export async function openEmployeeLease(options, initialSignal) {
|
|
32
|
+
initialSignal?.throwIfAborted();
|
|
33
|
+
const api = options.getConnect(), network = options.getNetwork(), lifetime = new AbortController(), offs = [];
|
|
34
|
+
if (!api || api.getContractInfo().localWorkspaceContext !== 1 || api.getContractInfo().workspaceLocations !== 1 || !api.getLocalWorkspaceContext || !api.getWorkspaceLocationState || !api.onWorkspaceLocationState)
|
|
35
|
+
throw fail('EMPLOYEE_CONTEXT_UNAVAILABLE');
|
|
36
|
+
if (!network || typeof network.prepareEmployeeEndpoint !== 'function' || typeof network.onState !== 'function')
|
|
37
|
+
throw fail('EMPLOYEE_ENDPOINT_UNAVAILABLE');
|
|
38
|
+
const workspace = await options.readWorkspace(), connection = api.getConnectionState(), state = api.getWorkspaceLocationState();
|
|
39
|
+
const items = state.items.filter(x => x.workspaceId === workspace.workspaceId);
|
|
40
|
+
if (connection.phase !== 'connected' || state.phase !== 'ready' || state.instanceId !== connection.instanceId || state.epoch !== connection.epoch || items.length !== 1 || items[0].source !== 'local' || items[0].status !== 'selected' || !items[0].bindingId)
|
|
41
|
+
throw fail();
|
|
42
|
+
const request = { instanceId: connection.instanceId, epoch: connection.epoch, workspaceId: workspace.workspaceId, expectedBindingId: items[0].bindingId };
|
|
43
|
+
let target, closed = false, diagnosticFailure;
|
|
44
|
+
const invalidate = (code) => { if (lifetime.signal.aborted)
|
|
45
|
+
return; try {
|
|
46
|
+
options.detail({ kind: 'session', severity: 'warning', code: 'CLAUDE_EMPLOYEE_REVOKED', summary: '本机旧绑定已停止派发,在途结果按实际确认;不会重放', payload: { reason: code, workspaceId: request.workspaceId, bindingId: request.expectedBindingId } });
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
diagnosticFailure ??= error;
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
lifetime.abort(diagnosticFailure ?? fail(code));
|
|
53
|
+
} };
|
|
54
|
+
const current = () => {
|
|
55
|
+
if (options.getConnect() !== api || options.getNetwork() !== network)
|
|
56
|
+
throw fail();
|
|
57
|
+
const c = api.getConnectionState(), s = api.getWorkspaceLocationState(), matches = s.items.filter(x => x.workspaceId === request.workspaceId);
|
|
58
|
+
if (c.phase !== 'connected' || c.instanceId !== request.instanceId || c.epoch !== request.epoch || s.phase !== 'ready' || s.instanceId !== request.instanceId || s.epoch !== request.epoch || matches.length !== 1 || matches[0].source !== 'local' || matches[0].status !== 'selected' || matches[0].bindingId !== request.expectedBindingId || network.getState().phase !== 'online')
|
|
59
|
+
throw fail();
|
|
60
|
+
lifetime.signal.throwIfAborted();
|
|
61
|
+
};
|
|
62
|
+
const changed = () => { try {
|
|
63
|
+
current();
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
invalidate('EMPLOYEE_CONTEXT_CHANGED');
|
|
67
|
+
} };
|
|
68
|
+
offs.push(api.onConnectionState(changed), api.onWorkspaceLocationState(changed), network.onState(event => { if (event?.kind === 'lost' || network.getState().phase !== 'online')
|
|
69
|
+
invalidate('EMPLOYEE_ENDPOINT_UNAVAILABLE'); }));
|
|
70
|
+
async function validate(callerSignal) {
|
|
71
|
+
if (diagnosticFailure)
|
|
72
|
+
throw diagnosticFailure;
|
|
73
|
+
callerSignal?.throwIfAborted();
|
|
74
|
+
const signal = AbortSignal.any([lifetime.signal, ...callerSignal ? [callerSignal] : []]);
|
|
75
|
+
try {
|
|
76
|
+
current();
|
|
77
|
+
const now = await options.readWorkspace();
|
|
78
|
+
signal.throwIfAborted();
|
|
79
|
+
current();
|
|
80
|
+
const stat = await lstat(workspace.directory.path), canonical = await realpath(workspace.directory.path);
|
|
81
|
+
if (!isDeepStrictEqual(now, workspace) || !stat.isDirectory() || stat.isSymbolicLink() || canonical !== workspace.directory.path || stat.dev !== workspace.directory.device || stat.ino !== workspace.directory.inode)
|
|
82
|
+
throw fail();
|
|
83
|
+
const context = unwrap(await api.getLocalWorkspaceContext(request));
|
|
84
|
+
signal.throwIfAborted();
|
|
85
|
+
current();
|
|
86
|
+
if (context.version !== 1 || context.source !== 'local' || context.instanceId !== request.instanceId || context.epoch !== request.epoch || context.workspaceId !== request.workspaceId || context.bindingId !== request.expectedBindingId || !isDeepStrictEqual(context.directory, workspace.directory))
|
|
87
|
+
throw fail();
|
|
88
|
+
const prepared = await network.prepareEmployeeEndpoint({ ...request }, signal);
|
|
89
|
+
signal.throwIfAborted();
|
|
90
|
+
current();
|
|
91
|
+
if (!prepared.ok)
|
|
92
|
+
throw fail(/^[A-Z][A-Z0-9_]*$/.test(prepared.error.code) ? prepared.error.code : 'EMPLOYEE_ENDPOINT_UNAVAILABLE');
|
|
93
|
+
const next = structuredClone(prepared.value);
|
|
94
|
+
verifyEmployeeTarget(next, request, workspace.directory, options.host);
|
|
95
|
+
if (target && !isDeepStrictEqual(next, target))
|
|
96
|
+
throw fail();
|
|
97
|
+
const host = unwrap(await api.getSshTarget());
|
|
98
|
+
if (!sameTarget(host, options.host))
|
|
99
|
+
throw fail();
|
|
100
|
+
const raw = await options.control(host, endpointCommand(next), signal);
|
|
101
|
+
signal.throwIfAborted();
|
|
102
|
+
current();
|
|
103
|
+
let result;
|
|
104
|
+
try {
|
|
105
|
+
result = JSON.parse(raw);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
throw fail('EMPLOYEE_BRIDGE_PROTOCOL_INVALID');
|
|
109
|
+
}
|
|
110
|
+
verifyBridgeTarget(result, next);
|
|
111
|
+
const final = await lstat(workspace.directory.path);
|
|
112
|
+
signal.throwIfAborted();
|
|
113
|
+
current();
|
|
114
|
+
if (final.dev !== workspace.directory.device || final.ino !== workspace.directory.inode)
|
|
115
|
+
throw fail();
|
|
116
|
+
target ??= next;
|
|
117
|
+
return structuredClone(target);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
if (!callerSignal?.aborted)
|
|
121
|
+
invalidate(error instanceof NeedError ? error.code : 'EMPLOYEE_CONTEXT_CHANGED');
|
|
122
|
+
throw error instanceof NeedError || callerSignal?.aborted ? error : fail();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
await validate(initialSignal);
|
|
127
|
+
return { target: structuredClone(target), request: Object.freeze({ ...request }), signal: lifetime.signal, validate, close: async () => { if (!closed) {
|
|
128
|
+
closed = true;
|
|
129
|
+
lifetime.abort(fail('EMPLOYEE_EXECUTOR_CLOSED'));
|
|
130
|
+
for (const off of offs)
|
|
131
|
+
off();
|
|
132
|
+
} if (diagnosticFailure)
|
|
133
|
+
throw diagnosticFailure; } };
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
for (const off of offs)
|
|
137
|
+
off();
|
|
138
|
+
lifetime.abort();
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ChildProcessWithoutNullStreams } from 'node:child_process';
|
|
2
|
+
import { NeedError } from './remote.ts';
|
|
3
|
+
export type EmployeeMessage = Record<string, unknown>;
|
|
4
|
+
export interface EmployeeExecutionTransport {
|
|
5
|
+
write(message: EmployeeMessage, signal?: AbortSignal): Promise<void>;
|
|
6
|
+
read(signal?: AbortSignal): Promise<EmployeeMessage | null>;
|
|
7
|
+
/** Closing a carrier never proves that an employee command completed. */
|
|
8
|
+
close(): void | Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export interface EmployeeExecutionOptions {
|
|
11
|
+
bindingId: string;
|
|
12
|
+
acpMaxMessageBytes: number;
|
|
13
|
+
/** Rechecks the current L CAS/directory/account and N/S five-tuple. No cached permit. */
|
|
14
|
+
validate(signal: AbortSignal): Promise<void>;
|
|
15
|
+
/** Once allocation starts, settle only after returning its executor or confirming aborted cleanup. */
|
|
16
|
+
transport(signal: AbortSignal): Promise<EmployeeExecutionTransport>;
|
|
17
|
+
}
|
|
18
|
+
export interface EmployeeExecutionResult {
|
|
19
|
+
version: 1;
|
|
20
|
+
executionId?: string;
|
|
21
|
+
messages?: EmployeeMessage[];
|
|
22
|
+
closed?: boolean;
|
|
23
|
+
result?: 'UNKNOWN';
|
|
24
|
+
}
|
|
25
|
+
/** One immutable workspace binding owns one executor. UNKNOWN is terminal, never replayed. */
|
|
26
|
+
export declare class EmployeeExecution {
|
|
27
|
+
private readonly options;
|
|
28
|
+
private readonly lifetime;
|
|
29
|
+
private executionId?;
|
|
30
|
+
private io?;
|
|
31
|
+
private opening;
|
|
32
|
+
private ended;
|
|
33
|
+
private error?;
|
|
34
|
+
private pendingRead;
|
|
35
|
+
private writeTail;
|
|
36
|
+
private queuedWriteBytes;
|
|
37
|
+
private closing;
|
|
38
|
+
private factoryReceipt;
|
|
39
|
+
constructor(options: EmployeeExecutionOptions);
|
|
40
|
+
private parse;
|
|
41
|
+
private active;
|
|
42
|
+
private gate;
|
|
43
|
+
private stop;
|
|
44
|
+
private closeTransport;
|
|
45
|
+
invalidate(error?: NeedError): void;
|
|
46
|
+
private cleanup;
|
|
47
|
+
dispose(): Promise<void>;
|
|
48
|
+
handle(params: unknown, signal?: AbortSignal): Promise<EmployeeExecutionResult>;
|
|
49
|
+
private open;
|
|
50
|
+
private read;
|
|
51
|
+
}
|
|
52
|
+
/** NDJSON carrier with byte bounds and backpressure derived solely from acpMaxMessageBytes. */
|
|
53
|
+
export declare function createEmployeeProcessTransport(child: ChildProcessWithoutNullStreams, acpMaxMessageBytes: number, stderrCodes?: readonly string[]): EmployeeExecutionTransport;
|