@ai-workbench/remote-agent 0.8.5 → 0.8.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/README.md +49 -8
  2. package/dist/acp.d.ts +22 -2
  3. package/dist/acp.js +143 -25
  4. package/dist/approval-label.d.ts +1 -1
  5. package/dist/approval-label.js +15 -3
  6. package/dist/broker-runtime.js +40 -8
  7. package/dist/broker.js +5 -1
  8. package/dist/claude-local.d.ts +37 -0
  9. package/dist/claude-local.js +207 -0
  10. package/dist/client.js +71 -2
  11. package/dist/conversation-details.d.ts +0 -1
  12. package/dist/conversation-details.js +37 -7
  13. package/dist/employee-endpoint.d.ts +91 -0
  14. package/dist/employee-endpoint.js +141 -0
  15. package/dist/employee-exec.d.ts +53 -0
  16. package/dist/employee-exec.js +423 -0
  17. package/dist/employee-location.d.ts +66 -0
  18. package/dist/employee-location.js +51 -0
  19. package/dist/employee-mcp.d.ts +42 -0
  20. package/dist/employee-mcp.js +181 -0
  21. package/dist/employee-native.d.ts +18 -0
  22. package/dist/employee-native.js +38 -0
  23. package/dist/employee-provider.d.ts +42 -0
  24. package/dist/employee-provider.js +326 -0
  25. package/dist/employee-relay.d.ts +18 -0
  26. package/dist/employee-relay.js +49 -0
  27. package/dist/employee-shell.d.ts +26 -0
  28. package/dist/employee-shell.js +89 -0
  29. package/dist/employee-ssh.d.ts +22 -0
  30. package/dist/employee-ssh.js +123 -0
  31. package/dist/employee-tools.d.ts +117 -0
  32. package/dist/employee-tools.js +292 -0
  33. package/dist/employee-workspace.d.ts +26 -0
  34. package/dist/employee-workspace.js +61 -0
  35. package/dist/index.d.ts +6 -0
  36. package/dist/index.js +235 -41
  37. package/dist/input.d.ts +11 -0
  38. package/dist/input.js +61 -23
  39. package/dist/local-user-turn.d.ts +24 -0
  40. package/dist/local-user-turn.js +103 -0
  41. package/dist/model-catalog.d.ts +18 -2
  42. package/dist/model-catalog.js +101 -36
  43. package/dist/model-defaults.d.ts +29 -0
  44. package/dist/model-defaults.js +62 -0
  45. package/dist/model-input-retention.d.ts +16 -0
  46. package/dist/model-input-retention.js +129 -0
  47. package/dist/presentation.d.ts +1 -1
  48. package/dist/presentation.js +52 -2
  49. package/dist/provider.d.ts +39 -2
  50. package/dist/provider.js +69 -13
  51. package/dist/readiness.js +1 -1
  52. package/dist/recovery.d.ts +14 -1
  53. package/dist/recovery.js +11 -2
  54. package/dist/remote-details.d.ts +7 -0
  55. package/dist/remote-details.js +11 -1
  56. package/dist/workspace-source-confirmation.d.ts +4 -0
  57. package/dist/workspace-source-confirmation.js +12 -0
  58. package/dist/workspace-source.d.ts +25 -0
  59. package/dist/workspace-source.js +136 -0
  60. package/helpers/ripgrep/LICENSE-vscode +13 -0
  61. package/helpers/ripgrep/PROVENANCE.md +9 -0
  62. package/helpers/ripgrep/darwin-arm64/rg +0 -0
  63. package/helpers/ripgrep/darwin-x64/rg +0 -0
  64. package/helpers/ripgrep/manifest.json +56 -0
  65. package/helpers/ripgrep/notices/COPYING-ripgrep +3 -0
  66. package/helpers/ripgrep/notices/DEPENDENCIES.json +946 -0
  67. package/helpers/ripgrep/notices/LICENCE-PCRE2.md +103 -0
  68. package/helpers/ripgrep/notices/LICENSE-MIT-ripgrep +21 -0
  69. package/helpers/ripgrep/notices/UNLICENSE-ripgrep +24 -0
  70. package/helpers/ripgrep/notices/aho-corasick-1.1.3/COPYING +3 -0
  71. package/helpers/ripgrep/notices/aho-corasick-1.1.3/LICENSE-MIT +21 -0
  72. package/helpers/ripgrep/notices/aho-corasick-1.1.3/UNLICENSE +24 -0
  73. package/helpers/ripgrep/notices/anyhow-1.0.100/LICENSE-APACHE +176 -0
  74. package/helpers/ripgrep/notices/anyhow-1.0.100/LICENSE-MIT +23 -0
  75. package/helpers/ripgrep/notices/arbitrary-1.4.2/LICENSE-APACHE +201 -0
  76. package/helpers/ripgrep/notices/arbitrary-1.4.2/LICENSE-MIT +27 -0
  77. package/helpers/ripgrep/notices/bstr-1.12.0/COPYING +8 -0
  78. package/helpers/ripgrep/notices/bstr-1.12.0/LICENSE-APACHE +201 -0
  79. package/helpers/ripgrep/notices/bstr-1.12.0/LICENSE-MIT +21 -0
  80. package/helpers/ripgrep/notices/cc-1.2.41/LICENSE-APACHE +201 -0
  81. package/helpers/ripgrep/notices/cc-1.2.41/LICENSE-MIT +25 -0
  82. package/helpers/ripgrep/notices/cfg-if-1.0.4/LICENSE-APACHE +201 -0
  83. package/helpers/ripgrep/notices/cfg-if-1.0.4/LICENSE-MIT +25 -0
  84. package/helpers/ripgrep/notices/crossbeam-channel-0.5.15/LICENSE-APACHE +201 -0
  85. package/helpers/ripgrep/notices/crossbeam-channel-0.5.15/LICENSE-MIT +27 -0
  86. package/helpers/ripgrep/notices/crossbeam-channel-0.5.15/LICENSE-THIRD-PARTY +593 -0
  87. package/helpers/ripgrep/notices/crossbeam-deque-0.8.6/LICENSE-APACHE +201 -0
  88. package/helpers/ripgrep/notices/crossbeam-deque-0.8.6/LICENSE-MIT +27 -0
  89. package/helpers/ripgrep/notices/crossbeam-epoch-0.9.18/LICENSE-APACHE +201 -0
  90. package/helpers/ripgrep/notices/crossbeam-epoch-0.9.18/LICENSE-MIT +27 -0
  91. package/helpers/ripgrep/notices/crossbeam-utils-0.8.21/LICENSE-APACHE +201 -0
  92. package/helpers/ripgrep/notices/crossbeam-utils-0.8.21/LICENSE-MIT +27 -0
  93. package/helpers/ripgrep/notices/derive_arbitrary-1.4.2/LICENSE-APACHE +201 -0
  94. package/helpers/ripgrep/notices/derive_arbitrary-1.4.2/LICENSE-MIT +27 -0
  95. package/helpers/ripgrep/notices/encoding_rs-0.8.35/COPYRIGHT +17 -0
  96. package/helpers/ripgrep/notices/encoding_rs-0.8.35/LICENSE-APACHE +202 -0
  97. package/helpers/ripgrep/notices/encoding_rs-0.8.35/LICENSE-MIT +25 -0
  98. package/helpers/ripgrep/notices/encoding_rs-0.8.35/LICENSE-WHATWG +26 -0
  99. package/helpers/ripgrep/notices/encoding_rs_io-0.1.7/COPYING +8 -0
  100. package/helpers/ripgrep/notices/encoding_rs_io-0.1.7/LICENSE-APACHE +201 -0
  101. package/helpers/ripgrep/notices/encoding_rs_io-0.1.7/LICENSE-MIT +21 -0
  102. package/helpers/ripgrep/notices/find-msvc-tools-0.1.4/LICENSE-APACHE +201 -0
  103. package/helpers/ripgrep/notices/find-msvc-tools-0.1.4/LICENSE-MIT +25 -0
  104. package/helpers/ripgrep/notices/getrandom-0.3.4/LICENSE-APACHE +201 -0
  105. package/helpers/ripgrep/notices/getrandom-0.3.4/LICENSE-MIT +26 -0
  106. package/helpers/ripgrep/notices/glob-0.3.3/LICENSE-APACHE +201 -0
  107. package/helpers/ripgrep/notices/glob-0.3.3/LICENSE-MIT +25 -0
  108. package/helpers/ripgrep/notices/itoa-1.0.15/LICENSE-APACHE +176 -0
  109. package/helpers/ripgrep/notices/itoa-1.0.15/LICENSE-MIT +23 -0
  110. package/helpers/ripgrep/notices/jobserver-0.1.34/LICENSE-APACHE +201 -0
  111. package/helpers/ripgrep/notices/jobserver-0.1.34/LICENSE-MIT +25 -0
  112. package/helpers/ripgrep/notices/lexopt-0.3.1/LICENSE +19 -0
  113. package/helpers/ripgrep/notices/libc-0.2.177/LICENSE-APACHE +176 -0
  114. package/helpers/ripgrep/notices/libc-0.2.177/LICENSE-MIT +25 -0
  115. package/helpers/ripgrep/notices/log-0.4.28/LICENSE-APACHE +201 -0
  116. package/helpers/ripgrep/notices/log-0.4.28/LICENSE-MIT +25 -0
  117. package/helpers/ripgrep/notices/memchr-2.7.6/COPYING +3 -0
  118. package/helpers/ripgrep/notices/memchr-2.7.6/LICENSE-MIT +21 -0
  119. package/helpers/ripgrep/notices/memchr-2.7.6/UNLICENSE +24 -0
  120. package/helpers/ripgrep/notices/memmap2-0.9.8/LICENSE-APACHE +201 -0
  121. package/helpers/ripgrep/notices/memmap2-0.9.8/LICENSE-MIT +26 -0
  122. package/helpers/ripgrep/notices/msvc_spectre_libs-0.1.3/LICENSE +21 -0
  123. package/helpers/ripgrep/notices/pcre2-0.2.11/COPYING +3 -0
  124. package/helpers/ripgrep/notices/pcre2-0.2.11/LICENSE-MIT +21 -0
  125. package/helpers/ripgrep/notices/pcre2-0.2.11/UNLICENSE +24 -0
  126. package/helpers/ripgrep/notices/pcre2-sys-0.2.10/COPYING +3 -0
  127. package/helpers/ripgrep/notices/pcre2-sys-0.2.10/LICENSE-MIT +21 -0
  128. package/helpers/ripgrep/notices/pcre2-sys-0.2.10/UNLICENSE +24 -0
  129. package/helpers/ripgrep/notices/pkg-config-0.3.32/LICENSE-APACHE +201 -0
  130. package/helpers/ripgrep/notices/pkg-config-0.3.32/LICENSE-MIT +25 -0
  131. package/helpers/ripgrep/notices/proc-macro2-1.0.101/LICENSE-APACHE +176 -0
  132. package/helpers/ripgrep/notices/proc-macro2-1.0.101/LICENSE-MIT +23 -0
  133. package/helpers/ripgrep/notices/quote-1.0.41/LICENSE-APACHE +176 -0
  134. package/helpers/ripgrep/notices/quote-1.0.41/LICENSE-MIT +23 -0
  135. package/helpers/ripgrep/notices/r-efi-5.3.0/AUTHORS +74 -0
  136. package/helpers/ripgrep/notices/regex-1.12.2/LICENSE-APACHE +201 -0
  137. package/helpers/ripgrep/notices/regex-1.12.2/LICENSE-MIT +25 -0
  138. package/helpers/ripgrep/notices/regex-automata-0.4.13/LICENSE-APACHE +201 -0
  139. package/helpers/ripgrep/notices/regex-automata-0.4.13/LICENSE-MIT +25 -0
  140. package/helpers/ripgrep/notices/regex-syntax-0.8.8/LICENSE-APACHE +201 -0
  141. package/helpers/ripgrep/notices/regex-syntax-0.8.8/LICENSE-MIT +25 -0
  142. package/helpers/ripgrep/notices/regex-syntax-0.8.8/src/unicode_tables/LICENSE-UNICODE +57 -0
  143. package/helpers/ripgrep/notices/ryu-1.0.20/LICENSE-APACHE +176 -0
  144. package/helpers/ripgrep/notices/ryu-1.0.20/LICENSE-BOOST +23 -0
  145. package/helpers/ripgrep/notices/same-file-1.0.6/COPYING +3 -0
  146. package/helpers/ripgrep/notices/same-file-1.0.6/LICENSE-MIT +21 -0
  147. package/helpers/ripgrep/notices/same-file-1.0.6/UNLICENSE +24 -0
  148. package/helpers/ripgrep/notices/serde-1.0.228/LICENSE-APACHE +176 -0
  149. package/helpers/ripgrep/notices/serde-1.0.228/LICENSE-MIT +23 -0
  150. package/helpers/ripgrep/notices/serde_core-1.0.228/LICENSE-APACHE +176 -0
  151. package/helpers/ripgrep/notices/serde_core-1.0.228/LICENSE-MIT +23 -0
  152. package/helpers/ripgrep/notices/serde_derive-1.0.228/LICENSE-APACHE +176 -0
  153. package/helpers/ripgrep/notices/serde_derive-1.0.228/LICENSE-MIT +23 -0
  154. package/helpers/ripgrep/notices/serde_json-1.0.145/LICENSE-APACHE +176 -0
  155. package/helpers/ripgrep/notices/serde_json-1.0.145/LICENSE-MIT +23 -0
  156. package/helpers/ripgrep/notices/shlex-1.3.0/LICENSE-APACHE +13 -0
  157. package/helpers/ripgrep/notices/shlex-1.3.0/LICENSE-MIT +21 -0
  158. package/helpers/ripgrep/notices/syn-2.0.106/LICENSE-APACHE +176 -0
  159. package/helpers/ripgrep/notices/syn-2.0.106/LICENSE-MIT +23 -0
  160. package/helpers/ripgrep/notices/termcolor-1.4.1/COPYING +3 -0
  161. package/helpers/ripgrep/notices/termcolor-1.4.1/LICENSE-MIT +21 -0
  162. package/helpers/ripgrep/notices/termcolor-1.4.1/UNLICENSE +24 -0
  163. package/helpers/ripgrep/notices/textwrap-0.16.2/LICENSE +21 -0
  164. package/helpers/ripgrep/notices/tikv-jemalloc-sys-0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7/LICENSE-APACHE +201 -0
  165. package/helpers/ripgrep/notices/tikv-jemalloc-sys-0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7/LICENSE-MIT +25 -0
  166. package/helpers/ripgrep/notices/tikv-jemalloc-sys-0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7/jemalloc/COPYING +27 -0
  167. package/helpers/ripgrep/notices/tikv-jemallocator-0.6.0/LICENSE-APACHE +201 -0
  168. package/helpers/ripgrep/notices/tikv-jemallocator-0.6.0/LICENSE-MIT +25 -0
  169. package/helpers/ripgrep/notices/unicode-ident-1.0.19/LICENSE-APACHE +176 -0
  170. package/helpers/ripgrep/notices/unicode-ident-1.0.19/LICENSE-MIT +23 -0
  171. package/helpers/ripgrep/notices/unicode-ident-1.0.19/LICENSE-UNICODE +39 -0
  172. package/helpers/ripgrep/notices/walkdir-2.5.0/COPYING +3 -0
  173. package/helpers/ripgrep/notices/walkdir-2.5.0/LICENSE-MIT +21 -0
  174. package/helpers/ripgrep/notices/walkdir-2.5.0/UNLICENSE +24 -0
  175. package/helpers/ripgrep/notices/wasip2-1.0.1+wasi-0.2.4/LICENSE-APACHE +201 -0
  176. package/helpers/ripgrep/notices/wasip2-1.0.1+wasi-0.2.4/LICENSE-Apache-2.0_WITH_LLVM-exception +220 -0
  177. package/helpers/ripgrep/notices/wasip2-1.0.1+wasi-0.2.4/LICENSE-MIT +23 -0
  178. package/helpers/ripgrep/notices/winapi-util-0.1.11/COPYING +3 -0
  179. package/helpers/ripgrep/notices/winapi-util-0.1.11/LICENSE-MIT +21 -0
  180. package/helpers/ripgrep/notices/winapi-util-0.1.11/UNLICENSE +24 -0
  181. package/helpers/ripgrep/notices/windows-link-0.2.1/license-apache-2.0 +201 -0
  182. package/helpers/ripgrep/notices/windows-link-0.2.1/license-mit +21 -0
  183. package/helpers/ripgrep/notices/windows-sys-0.61.2/license-apache-2.0 +201 -0
  184. package/helpers/ripgrep/notices/windows-sys-0.61.2/license-mit +21 -0
  185. package/helpers/ripgrep/notices/wit-bindgen-0.46.0/LICENSE-APACHE +201 -0
  186. package/helpers/ripgrep/notices/wit-bindgen-0.46.0/LICENSE-Apache-2.0_WITH_LLVM-exception +220 -0
  187. package/helpers/ripgrep/notices/wit-bindgen-0.46.0/LICENSE-MIT +23 -0
  188. package/locale/en.json +1 -1
  189. package/locale/zh.json +2 -2
  190. package/package.json +29 -11
@@ -0,0 +1,49 @@
1
+ /** Runs on the host. Only target discovery and SSH stdio forwarding; never user tools. */
2
+ export const EMPLOYEE_TARGET_PYTHON = String.raw `
3
+ import os,sys,json,subprocess,selectors,shlex,re
4
+ def fail(code):
5
+ sys.stderr.write(json.dumps({"code":code})+"\n");sys.exit(254)
6
+ try:
7
+ x=json.loads(sys.argv[1]);e=x["endpoint"];b=x["bridge"];maximum=x["maxBytes"]
8
+ if not isinstance(maximum,int) or isinstance(maximum,bool) or maximum<1:fail("EMPLOYEE_RELAY_SETTINGS_INVALID")
9
+ if b["command"]!="/usr/local/aw-host/bin/aw-employee-bridge":fail("EMPLOYEE_RELAY_TARGET_INVALID")
10
+ argv=[b["command"],"target","--settings",b["settingsPath"],"--device",e["deviceId"],"--if-instance",b["bridgeInstanceId"],"--if-generation",str(b["generation"]),"--if-endpoint",e["endpointInstanceId"],"--if-context",e["executionContextId"]]
11
+ p=subprocess.Popen(argv,stdin=subprocess.DEVNULL,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
12
+ selector=selectors.DefaultSelector();selector.register(p.stdout,selectors.EVENT_READ,"out");selector.register(p.stderr,selectors.EVENT_READ,"err")
13
+ data={"out":bytearray(),"err":bytearray()}
14
+ while selector.get_map():
15
+ for key,_ in selector.select():
16
+ chunk=os.read(key.fileobj.fileno(),maximum+1)
17
+ if not chunk:selector.unregister(key.fileobj);continue
18
+ data[key.data].extend(chunk)
19
+ if len(data["out"])+len(data["err"])>maximum:
20
+ p.kill();p.wait();fail("EMPLOYEE_RELAY_CONTROL_TOO_LARGE")
21
+ selector.close()
22
+ if p.wait()!=0:
23
+ reason=re.fullmatch(r"BRIDGE_ERROR ([A-Z][A-Z0-9_]{0,63})\s*",data["err"].decode("utf-8",errors="strict"))
24
+ fail(reason.group(1) if reason else "EMPLOYEE_BRIDGE_TARGET_FAILED")
25
+ t=json.loads(data["out"])
26
+ if t.get("type")!="target" or t.get("protocolVersion")!=1 or t.get("endpoint",{}).get("state")!="online":fail("EMPLOYEE_BRIDGE_PROTOCOL_INVALID")
27
+ expected=dict(e,bridgeInstanceId=b["bridgeInstanceId"],generation=b["generation"])
28
+ if any(t["endpoint"].get(k)!=v for k,v in expected.items()):fail("EMPLOYEE_CONTEXT_CHANGED")
29
+ args=t.get("sshArgs")
30
+ if not isinstance(args,list) or not args or any(not isinstance(v,str) or "\x00" in v for v in args):fail("EMPLOYEE_BRIDGE_PROTOCOL_INVALID")
31
+ `;
32
+ export const EMPLOYEE_PYTHON_ERRORS = String.raw `
33
+ except (KeyError,ValueError,TypeError,OSError):
34
+ fail("EMPLOYEE_RELAY_FAILED")
35
+ `;
36
+ export const EMPLOYEE_RELAY_PYTHON = EMPLOYEE_TARGET_PYTHON + String.raw `
37
+ socket=x["socketPath"]
38
+ if not isinstance(socket,str) or not socket.startswith("/") or "\x00" in socket:fail("EMPLOYEE_RELAY_SOCKET_INVALID")
39
+ command=shlex.join(["/usr/bin/nc","-U",socket])
40
+ os.execv("/usr/bin/ssh",["/usr/bin/ssh","-T"]+args+[command])
41
+ ` + EMPLOYEE_PYTHON_ERRORS;
42
+ export function employeeRouting(target, socketPath, maxBytes) {
43
+ const names = ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash'];
44
+ return {
45
+ // ACP0.94.0 uses absence of "type" to recognize stdio. Do not add type:"stdio".
46
+ mcpServers: [{ name: 'employee', command: '/usr/bin/python3', args: ['-c', EMPLOYEE_RELAY_PYTHON, JSON.stringify({ endpoint: target.endpoint, bridge: target.bridge, socketPath, maxBytes })], env: [] }],
47
+ toolAliases: Object.fromEntries(names.map(name => [name, 'mcp__employee__' + name])),
48
+ };
49
+ }
@@ -0,0 +1,26 @@
1
+ import type { ChildProcessWithoutNullStreams } from 'node:child_process';
2
+ import type { EmployeeEndpointTarget } from './employee-endpoint.ts';
3
+ export interface EmployeeShellResult {
4
+ stdout: string;
5
+ stderr: string;
6
+ stderrSource: 'combined-ssh-and-command';
7
+ exitCode: number;
8
+ signal: null;
9
+ }
10
+ export type EmployeeShell = (request: {
11
+ command: string;
12
+ timeout?: number;
13
+ signal: AbortSignal;
14
+ }) => Promise<EmployeeShellResult>;
15
+ interface Options {
16
+ target: EmployeeEndpointTarget;
17
+ maxBytes: number;
18
+ killGraceMs: number;
19
+ attach: (spec: {
20
+ command: '/usr/bin/python3';
21
+ args: string[];
22
+ }) => Promise<ChildProcessWithoutNullStreams>;
23
+ }
24
+ /** Every invocation re-discovers the exact S target. Cancellation cannot prove remote exit. */
25
+ export declare function createEmployeeShell(options: Options): EmployeeShell;
26
+ export {};
@@ -0,0 +1,89 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { EMPLOYEE_TARGET_PYTHON, EMPLOYEE_PYTHON_ERRORS } from "./employee-relay.js";
3
+ import { NeedError } from "./remote.js";
4
+ const SHELL_PYTHON = EMPLOYEE_TARGET_PYTHON + String.raw `
5
+ cwd=x["directory"]["path"];device=x["directory"]["device"];inode=x["directory"]["inode"]
6
+ # User source is stdin, never argv. The sentinel preserves trailing newlines.
7
+ wrapper="cd -- "+shlex.quote(cwd)+" || exit 254; "+'test "$(/usr/bin/stat -f '+shlex.quote('%d %i')+' .)" = '+shlex.quote(str(device)+' '+str(inode))+" || exit 254; "+'test "$(/usr/bin/id -u)" = '+shlex.quote(str(e['remoteUid']))+" || exit 254; "+"printf '%s\\n' "+shlex.quote('AW_EMPLOYEE_SHELL_STARTED '+x['executionId'])+" >&2; "+'aw_x_source=$(/bin/cat; printf x); aw_x_source='+chr(36)+'{aw_x_source%?}; eval "'+chr(36)+'aw_x_source"'
8
+ command=shlex.join([e["endpointShell"],"-c",wrapper])
9
+ sys.stderr.write("AW_EMPLOYEE_SHELL_READY "+x["executionId"]+"\n");sys.stderr.flush()
10
+ p=subprocess.Popen(["/usr/bin/ssh","-T"]+args+[command],stdin=sys.stdin,stdout=sys.stdout,stderr=sys.stderr)
11
+ code=p.wait();sys.exit(code if code>=0 else 255)
12
+ ` + EMPLOYEE_PYTHON_ERRORS;
13
+ const unknown = (code) => new NeedError(code, '本机命令结果未知;没有重放,也未改在主机执行。', '员工端真实退出回执');
14
+ /** Every invocation re-discovers the exact S target. Cancellation cannot prove remote exit. */
15
+ export function createEmployeeShell(options) {
16
+ return async ({ command, timeout, signal }) => {
17
+ signal.throwIfAborted();
18
+ const executionId = randomUUID(), target = options.target;
19
+ const child = await options.attach({ command: '/usr/bin/python3', args: ['-c', SHELL_PYTHON, JSON.stringify({ endpoint: target.endpoint, bridge: target.bridge, directory: target.local.directory, maxBytes: options.maxBytes, executionId })] });
20
+ return new Promise((yes, no) => {
21
+ let settled = false, bytes = 0, stdout = [], stderr = [], reason, grace, timer;
22
+ const finish = (error, code) => {
23
+ if (settled)
24
+ return;
25
+ settled = true;
26
+ signal.removeEventListener('abort', abort);
27
+ if (grace)
28
+ clearTimeout(grace);
29
+ if (timer)
30
+ clearTimeout(timer);
31
+ child.stdin.destroy();
32
+ child.stdout.destroy();
33
+ child.stderr.destroy();
34
+ if (error) {
35
+ no(error);
36
+ return;
37
+ }
38
+ let out, err;
39
+ try {
40
+ out = new TextDecoder('utf8', { fatal: true, ignoreBOM: true }).decode(Buffer.concat(stdout));
41
+ err = new TextDecoder('utf8', { fatal: true, ignoreBOM: true }).decode(Buffer.concat(stderr));
42
+ }
43
+ catch {
44
+ no(unknown('EMPLOYEE_OUTPUT_BINARY_UNSUPPORTED'));
45
+ return;
46
+ }
47
+ const header = 'AW_EMPLOYEE_SHELL_READY ' + executionId + '\n', started = 'AW_EMPLOYEE_SHELL_STARTED ' + executionId + '\n';
48
+ const start = err.indexOf(started, header.length);
49
+ if (!err.startsWith(header) || start < header.length || start > header.length && err[start - 1] !== '\n') {
50
+ const raw = err.startsWith(header) ? err.slice(header.length) : err;
51
+ let source;
52
+ try {
53
+ const parsed = JSON.parse(raw);
54
+ if (typeof parsed.code === 'string' && /^[A-Z][A-Z0-9_]{0,63}$/.test(parsed.code))
55
+ source = parsed.code;
56
+ }
57
+ catch { /* Never expose arbitrary transport diagnostics. */ }
58
+ no(unknown(source ?? 'EMPLOYEE_EXIT_UNKNOWN'));
59
+ return;
60
+ }
61
+ if (code === null || code === undefined || code === 255) {
62
+ no(unknown('EMPLOYEE_EXIT_UNKNOWN'));
63
+ return;
64
+ }
65
+ yes({ stdout: out, stderr: err.slice(header.length, start) + err.slice(start + started.length), stderrSource: 'combined-ssh-and-command', exitCode: code, signal: null });
66
+ };
67
+ const stop = (code) => { if (settled || reason)
68
+ return; reason = unknown(code); child.kill('SIGTERM'); grace = setTimeout(() => { if (child.exitCode === null && child.signalCode === null)
69
+ child.kill('SIGKILL'); finish(reason); }, options.killGraceMs); };
70
+ const abort = () => stop('EMPLOYEE_CANCELLED_UNKNOWN');
71
+ const collect = (destination, chunk) => { bytes += chunk.length; if (bytes > options.maxBytes) {
72
+ stop('EMPLOYEE_OUTPUT_TOO_LARGE_UNKNOWN');
73
+ return;
74
+ } destination.push(chunk); };
75
+ child.stdout.on('data', (b) => collect(stdout, b));
76
+ child.stderr.on('data', (b) => collect(stderr, b));
77
+ child.stdin.on('error', () => stop('EMPLOYEE_INPUT_UNKNOWN'));
78
+ child.once('error', () => { finish(unknown('EMPLOYEE_TRANSPORT_UNKNOWN')); });
79
+ child.once('close', code => finish(reason, code));
80
+ signal.addEventListener('abort', abort, { once: true });
81
+ if (signal.aborted)
82
+ abort();
83
+ if (timeout !== undefined)
84
+ timer = setTimeout(() => stop('EMPLOYEE_TIMEOUT_UNKNOWN'), timeout);
85
+ if (!reason)
86
+ child.stdin.end(command);
87
+ });
88
+ };
89
+ }
@@ -0,0 +1,22 @@
1
+ import type { EmployeeEndpointTarget } from '@ai-workbench/network/api';
2
+ import { type BrokerIo, type SshArgv } from './broker.ts';
3
+ import type { ConnectTarget } from './provider.ts';
4
+ import { type EmployeeExecutionTransport } from './employee-exec.ts';
5
+ export interface EmployeeSshOptions {
6
+ target: ConnectTarget;
7
+ endpoint: EmployeeEndpointTarget;
8
+ /** Automatically discovered package binary path on this same employee device. */
9
+ employeeBinary: string;
10
+ binarySha256: string;
11
+ acpMaxMessageBytes: number;
12
+ argv: SshArgv;
13
+ validate(signal: AbortSignal): Promise<void>;
14
+ /** Fixture seam only. Production uses L's exported argv through sshIo. */
15
+ io?: BrokerIo;
16
+ }
17
+ /** Fixed bootstrap only; cwd for every native operation remains in its original RPC. */
18
+ export declare function employeeExecServerCommand(binary: string, binarySha256: string): string;
19
+ /** Invoke this in EmployeeExecution.validate before and after each native RPC write. */
20
+ export declare function validateEmployeeSshTarget(options: EmployeeSshOptions, signal?: AbortSignal): Promise<void>;
21
+ /** Outer L-authenticated host SSH, then S-authenticated employee SSH; no host execution fallback. */
22
+ export declare function createEmployeeSshTransport(options: EmployeeSshOptions, signal?: AbortSignal): Promise<EmployeeExecutionTransport>;
@@ -0,0 +1,123 @@
1
+ import { sshIo } from "./broker.js";
2
+ import { createEmployeeProcessTransport } from "./employee-exec.js";
3
+ import { NeedError, shellQuote } from "./remote.js";
4
+ const fail = (code = 'EMPLOYEE_BRIDGE_PROTOCOL_INVALID') => new NeedError(code, '员工执行目标无法核验(' + code + '),没有改用主机执行或重试', '当前 L/N/S 同代员工端点及固定员工执行器');
5
+ const object = (value) => !!value && typeof value === 'object' && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
6
+ const exact = (value, keys) => object(value) && Object.keys(value).length === keys.length && keys.every(key => Object.hasOwn(value, key));
7
+ const text = (value) => typeof value === 'string' && value.length > 0 && !value.includes('\0');
8
+ const absolute = (value) => text(value) && value.startsWith('/');
9
+ const integer = (value, min = 0, max = Number.MAX_SAFE_INTEGER) => typeof value === 'number' && Number.isSafeInteger(value) && value >= min && value <= max;
10
+ 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}$/i.test(value);
11
+ const hash = (value) => typeof value === 'string' && /^[a-f0-9]{64}$/.test(value);
12
+ function checkSignal(signal) { if (signal.aborted)
13
+ throw fail('ABORTED'); }
14
+ function awaitSignal(promise, signal) {
15
+ if (signal.aborted) {
16
+ void promise.catch(() => { });
17
+ return Promise.reject(fail('ABORTED'));
18
+ }
19
+ return new Promise((resolve, reject) => {
20
+ const abort = () => { signal.removeEventListener('abort', abort); reject(fail('ABORTED')); };
21
+ signal.addEventListener('abort', abort, { once: true });
22
+ void promise.then(value => { signal.removeEventListener('abort', abort); resolve(value); }, error => { signal.removeEventListener('abort', abort); reject(error); });
23
+ if (signal.aborted)
24
+ abort();
25
+ });
26
+ }
27
+ function checkEndpoint(endpoint, target) {
28
+ if (endpoint?.version !== 1 || !endpoint.local || !endpoint.endpoint || !endpoint.bridge || endpoint.bridge.protocolVersion !== 1 || endpoint.bridge.command !== '/usr/local/aw-host/bin/aw-employee-bridge' || !absolute(endpoint.bridge.settingsPath) || endpoint.bridge.settingsPath !== target.session.settingsPath || !uuid(endpoint.bridge.bridgeInstanceId) || !integer(endpoint.bridge.generation, 1) || !hash(endpoint.endpoint.deviceId) || !uuid(endpoint.endpoint.endpointInstanceId) || !uuid(endpoint.endpoint.executionContextId) || !text(endpoint.endpoint.remoteUser) || !integer(endpoint.endpoint.remoteUid, 1, 4294967294) || ![endpoint.endpoint.remoteHome, endpoint.endpoint.endpointCwd, endpoint.endpoint.endpointShell, endpoint.local.directory?.path].every(absolute) || !text(endpoint.local.bindingId) || !text(endpoint.local.workspaceId) || !text(endpoint.local.instanceId) || !integer(endpoint.local.epoch) || !integer(endpoint.local.directory.device) || !integer(endpoint.local.directory.inode))
29
+ throw fail();
30
+ }
31
+ function policySource(value, scope) {
32
+ return exact(value, ['path', 'sha256', 'development', 'scope']) && absolute(value.path) && hash(value.sha256) && typeof value.development === 'boolean' && value.scope === scope;
33
+ }
34
+ function policy(value) {
35
+ const keys = ['controlMaxBytes', 'handshakeTimeoutMs', 'registerLeaseMs', 'heartbeatIntervalMs', 'maxHandshakeConnections', 'controlQueueMaxBytes', 'maxPendingStreams', 'maxActiveStreams', 'relayHighWaterBytes', 'relayLowWaterBytes', 'relayReadBytes'];
36
+ if (!exact(value, keys) || !keys.every(key => integer(value[key], key === 'relayLowWaterBytes' ? 0 : 1)))
37
+ return false;
38
+ const p = value;
39
+ return p.controlMaxBytes >= 1024 && p.controlMaxBytes <= 16777216 && p.controlQueueMaxBytes >= p.controlMaxBytes && p.controlQueueMaxBytes <= 16777216 && p.registerLeaseMs >= 3 * p.heartbeatIntervalMs && p.relayHighWaterBytes <= 16777216 && p.relayLowWaterBytes <= p.relayHighWaterBytes && p.relayReadBytes <= p.relayHighWaterBytes && ['handshakeTimeoutMs', 'registerLeaseMs', 'heartbeatIntervalMs', 'maxHandshakeConnections', 'maxPendingStreams', 'maxActiveStreams'].every(key => p[key] <= 2147483647);
40
+ }
41
+ function readTarget(raw, expected, maxBytes) {
42
+ if (Buffer.byteLength(raw) > maxBytes)
43
+ throw fail('ACP_MESSAGE_TOO_LARGE');
44
+ let value;
45
+ try {
46
+ value = JSON.parse(raw);
47
+ }
48
+ catch {
49
+ throw fail();
50
+ }
51
+ if (!exact(value, ['type', 'protocolVersion', 'endpoint', 'sshArgs', 'clientPolicySource']) || value.type !== 'target' || value.protocolVersion !== 1 || !policySource(value.clientPolicySource, 'client-handshake') || value.clientPolicySource.path !== expected.bridge.settingsPath)
52
+ throw fail();
53
+ const actual = value.endpoint;
54
+ const keys = ['deviceId', 'bridgeInstanceId', 'generation', 'endpointInstanceId', 'executionContextId', 'remoteUser', 'remoteUid', 'remoteHome', 'endpointCwd', 'endpointShell', 'state', 'policy', 'policySource', 'remainingLeaseMs', 'activeStreams', 'pendingStreams', 'clientPublicKey', 'hostKeyAlias', 'socketPath'];
55
+ if (!exact(actual, keys) || actual.state !== 'online' || !policy(actual.policy) || !policySource(actual.policySource, 'registration') || actual.policySource.path !== expected.bridge.settingsPath || !integer(actual.remainingLeaseMs) || !integer(actual.activeStreams) || !integer(actual.pendingStreams) || !text(actual.clientPublicKey) || !text(actual.hostKeyAlias) || !absolute(actual.socketPath))
56
+ throw fail();
57
+ for (const key of ['deviceId', 'endpointInstanceId', 'executionContextId', 'remoteUser', 'remoteUid', 'remoteHome', 'endpointCwd', 'endpointShell'])
58
+ if (actual[key] !== expected.endpoint[key])
59
+ throw fail('EMPLOYEE_CONTEXT_CHANGED');
60
+ if (actual.bridgeInstanceId !== expected.bridge.bridgeInstanceId || actual.generation !== expected.bridge.generation)
61
+ throw fail('EMPLOYEE_CONTEXT_CHANGED');
62
+ const args = value.sshArgs;
63
+ // S0.4.11 d6cdcc7 lib/employee_bridge.py:ssh_args already includes the destination.
64
+ if (!Array.isArray(args) || args.length !== 23 || !args.every(text))
65
+ throw fail();
66
+ const fixed = new Map([[0, '-F'], [1, '/dev/null'], [2, '-o'], [3, 'BatchMode=yes'], [4, '-o'], [5, 'IdentitiesOnly=yes'], [6, '-o'], [7, 'StrictHostKeyChecking=yes'], [8, '-o'], [9, 'IdentityAgent=none'], [10, '-o'], [12, '-o'], [13, 'GlobalKnownHostsFile=/dev/null'], [14, '-o'], [15, 'HostKeyAlias=' + actual.hostKeyAlias], [16, '-o'], [18, '-o'], [20, '-l'], [21, expected.endpoint.remoteUser], [22, 'aw-employee-endpoint']]);
67
+ if ([...fixed].some(([i, wanted]) => args[i] !== wanted) || !args[11].startsWith('UserKnownHostsFile="/') || !args[11].endsWith('"') || !args[17].startsWith('ProxyCommand=') || args[17].length <= 'ProxyCommand='.length || !args[19].startsWith('IdentityFile="/') || !args[19].endsWith('"'))
68
+ throw fail();
69
+ return args;
70
+ }
71
+ /** Fixed bootstrap only; cwd for every native operation remains in its original RPC. */
72
+ export function employeeExecServerCommand(binary, binarySha256) {
73
+ if (!absolute(binary) || !hash(binarySha256))
74
+ throw fail('EMPLOYEE_BINARY_REQUIRED');
75
+ const file = shellQuote(binary), digest = shellQuote(binarySha256);
76
+ const stop = (code) => `{ printf '%s\\n' '${code}' >&2; exit 126; }`;
77
+ return `if [ ! -f ${file} ] || [ ! -x ${file} ]; then ${stop('AW_EMPLOYEE_BINARY_UNAVAILABLE')}; fi; ` +
78
+ `aw_employee_digest=$(/usr/bin/shasum -a 256 < ${file} 2>/dev/null) || ${stop('AW_EMPLOYEE_BINARY_HASH_FAILED')}; ` +
79
+ `aw_employee_digest=\${aw_employee_digest%% *}; [ "$aw_employee_digest" = ${digest} ] || ${stop('AW_EMPLOYEE_BINARY_HASH_MISMATCH')}; ` +
80
+ `aw_employee_version=$(${file} --version 2>/dev/null) || ${stop('AW_EMPLOYEE_BINARY_VERSION_FAILED')}; ` +
81
+ `[ "$aw_employee_version" = 'codex-cli 0.160.1' ] || ${stop('AW_EMPLOYEE_BINARY_VERSION_MISMATCH')}; ` +
82
+ `exec ${file} exec-server --listen stdio`;
83
+ }
84
+ async function currentTarget(options, signal) {
85
+ checkSignal(signal);
86
+ if (!Number.isSafeInteger(options.acpMaxMessageBytes) || options.acpMaxMessageBytes < 1)
87
+ throw fail('SETTINGS_REQUIRED');
88
+ const endpoint = structuredClone(options.endpoint), target = structuredClone(options.target);
89
+ checkEndpoint(endpoint, target);
90
+ const io = options.io ?? sshIo(target, options.argv, options.acpMaxMessageBytes);
91
+ const gate = async () => { checkSignal(signal); await awaitSignal(options.validate(signal), signal); checkSignal(signal); };
92
+ await gate();
93
+ const b = endpoint.bridge, e = endpoint.endpoint;
94
+ const command = [b.command, 'target', '--settings', b.settingsPath, '--device', e.deviceId, '--if-instance', b.bridgeInstanceId, '--if-generation', String(b.generation), '--if-endpoint', e.endpointInstanceId, '--if-context', e.executionContextId].map(shellQuote).join(' ');
95
+ const raw = await awaitSignal(io.run(command, signal), signal);
96
+ const args = readTarget(raw, endpoint, options.acpMaxMessageBytes);
97
+ await gate();
98
+ return { io, args, gate };
99
+ }
100
+ /** Invoke this in EmployeeExecution.validate before and after each native RPC write. */
101
+ export async function validateEmployeeSshTarget(options, signal = new AbortController().signal) { await currentTarget(options, signal); }
102
+ /** Outer L-authenticated host SSH, then S-authenticated employee SSH; no host execution fallback. */
103
+ export async function createEmployeeSshTransport(options, signal = new AbortController().signal) {
104
+ checkSignal(signal);
105
+ const remote = employeeExecServerCommand(options.employeeBinary, options.binarySha256);
106
+ const { io, args, gate } = await currentTarget(options, signal);
107
+ checkSignal(signal);
108
+ // EmployeeExecution races the user's open wait; this factory retains any late child cleanup.
109
+ const child = await io.attach('exec ' + ['/usr/bin/ssh', ...args, remote].map(shellQuote).join(' '));
110
+ const transport = createEmployeeProcessTransport(child, options.acpMaxMessageBytes, ['AW_EMPLOYEE_BINARY_UNAVAILABLE', 'AW_EMPLOYEE_BINARY_HASH_FAILED', 'AW_EMPLOYEE_BINARY_HASH_MISMATCH', 'AW_EMPLOYEE_BINARY_VERSION_FAILED', 'AW_EMPLOYEE_BINARY_VERSION_MISMATCH']);
111
+ if (signal.aborted) {
112
+ await transport.close();
113
+ throw fail('ABORTED');
114
+ }
115
+ try {
116
+ await gate();
117
+ return transport;
118
+ }
119
+ catch (error) {
120
+ await transport.close();
121
+ throw error;
122
+ }
123
+ }
@@ -0,0 +1,117 @@
1
+ import type { DetailObservation } from './conversation-details.ts';
2
+ import type { EmployeeShell } from './employee-shell.ts';
3
+ export declare const EMPLOYEE_TOOL_NAMES: readonly ["Read", "Write", "Edit", "Glob", "Grep", "Bash"];
4
+ export type EmployeeToolName = typeof EMPLOYEE_TOOL_NAMES[number];
5
+ interface Options {
6
+ cwd: string;
7
+ shell: string;
8
+ uid: number;
9
+ maxBytes: number;
10
+ killGraceMs: number;
11
+ validate: (signal?: AbortSignal) => Promise<void>;
12
+ detail: (event: DetailObservation) => void;
13
+ rgPath?: string;
14
+ runShell?: EmployeeShell;
15
+ }
16
+ /** Executed only in the employee DSH process; never creates a host fallback. */
17
+ export declare function createEmployeeTools(options: Options): {
18
+ call(name: string, input: unknown, requestSignal?: AbortSignal, observer?: Options["detail"]): Promise<{
19
+ [x: string]: unknown;
20
+ content: ({
21
+ type: "text";
22
+ text: string;
23
+ annotations?: {
24
+ audience?: ("assistant" | "user")[] | undefined;
25
+ priority?: number | undefined;
26
+ lastModified?: string | undefined;
27
+ } | undefined;
28
+ _meta?: {
29
+ [x: string]: unknown;
30
+ } | undefined;
31
+ } | {
32
+ type: "image";
33
+ data: string;
34
+ mimeType: string;
35
+ annotations?: {
36
+ audience?: ("assistant" | "user")[] | undefined;
37
+ priority?: number | undefined;
38
+ lastModified?: string | undefined;
39
+ } | undefined;
40
+ _meta?: {
41
+ [x: string]: unknown;
42
+ } | undefined;
43
+ } | {
44
+ type: "audio";
45
+ data: string;
46
+ mimeType: string;
47
+ annotations?: {
48
+ audience?: ("assistant" | "user")[] | undefined;
49
+ priority?: number | undefined;
50
+ lastModified?: string | undefined;
51
+ } | undefined;
52
+ _meta?: {
53
+ [x: string]: unknown;
54
+ } | undefined;
55
+ } | {
56
+ uri: string;
57
+ name: string;
58
+ type: "resource_link";
59
+ description?: string | undefined;
60
+ mimeType?: string | undefined;
61
+ size?: number | undefined;
62
+ annotations?: {
63
+ audience?: ("assistant" | "user")[] | undefined;
64
+ priority?: number | undefined;
65
+ lastModified?: string | undefined;
66
+ } | undefined;
67
+ _meta?: {
68
+ [x: string]: unknown;
69
+ } | undefined;
70
+ icons?: {
71
+ src: string;
72
+ mimeType?: string | undefined;
73
+ sizes?: string[] | undefined;
74
+ theme?: "light" | "dark" | undefined;
75
+ }[] | undefined;
76
+ title?: string | undefined;
77
+ } | {
78
+ type: "resource";
79
+ resource: {
80
+ uri: string;
81
+ text: string;
82
+ mimeType?: string | undefined;
83
+ _meta?: {
84
+ [x: string]: unknown;
85
+ } | undefined;
86
+ } | {
87
+ uri: string;
88
+ blob: string;
89
+ mimeType?: string | undefined;
90
+ _meta?: {
91
+ [x: string]: unknown;
92
+ } | undefined;
93
+ };
94
+ annotations?: {
95
+ audience?: ("assistant" | "user")[] | undefined;
96
+ priority?: number | undefined;
97
+ lastModified?: string | undefined;
98
+ } | undefined;
99
+ _meta?: {
100
+ [x: string]: unknown;
101
+ } | undefined;
102
+ })[];
103
+ _meta?: {
104
+ [x: string]: unknown;
105
+ progressToken?: string | number | undefined;
106
+ "io.modelcontextprotocol/related-task"?: {
107
+ taskId: string;
108
+ } | undefined;
109
+ } | undefined;
110
+ structuredContent?: {
111
+ [x: string]: unknown;
112
+ } | undefined;
113
+ isError?: boolean | undefined;
114
+ }>;
115
+ close(): Promise<void>;
116
+ };
117
+ export {};