@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,423 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { NeedError } from "./remote.js";
3
+ const object = (value) => !!value && typeof value === 'object' && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
4
+ const need = (code) => new NeedError(code, code === 'ABORTED' ? '员工执行等待已取消,命令结果需另行确认' : '员工执行通道无法继续(' + code + '),未重放请求', '当前员工工作区绑定、N/S端点与实际命令状态');
5
+ const failure = (error) => error instanceof NeedError ? error : need('EMPLOYEE_EXEC_UNKNOWN');
6
+ function capacity(value) { if (!Number.isSafeInteger(value) || value < 1)
7
+ throw new NeedError('SETTINGS_REQUIRED', '我需要有效的 acpMaxMessageBytes', '管理员确认 acpMaxMessageBytes'); }
8
+ function encoded(message, maxBytes) {
9
+ if (!object(message))
10
+ throw need('EMPLOYEE_EXEC_PROTOCOL_INVALID');
11
+ let json;
12
+ try {
13
+ json = JSON.stringify(message);
14
+ }
15
+ catch {
16
+ throw need('EMPLOYEE_EXEC_PROTOCOL_INVALID');
17
+ }
18
+ if (Buffer.byteLength(json) > maxBytes)
19
+ throw need('ACP_MESSAGE_TOO_LARGE');
20
+ // Freeze this operation's wire value before any async gate can yield.
21
+ const value = JSON.parse(json);
22
+ if (!object(value))
23
+ throw need('EMPLOYEE_EXEC_PROTOCOL_INVALID');
24
+ return { message: value, line: Buffer.from(json + '\n') };
25
+ }
26
+ function cancelled(signal) { if (signal?.aborted)
27
+ throw need('ABORTED'); }
28
+ function waiting(operation, signal) {
29
+ if (signal.aborted) {
30
+ void operation.catch(() => { });
31
+ return Promise.reject(need('ABORTED'));
32
+ }
33
+ return new Promise((resolve, reject) => {
34
+ const abort = () => { signal.removeEventListener('abort', abort); reject(need('ABORTED')); };
35
+ signal.addEventListener('abort', abort, { once: true });
36
+ void operation.then(value => { signal.removeEventListener('abort', abort); resolve(value); }, error => { signal.removeEventListener('abort', abort); reject(error); });
37
+ if (signal.aborted)
38
+ abort();
39
+ });
40
+ }
41
+ /** One immutable workspace binding owns one executor. UNKNOWN is terminal, never replayed. */
42
+ export class EmployeeExecution {
43
+ options;
44
+ lifetime = new AbortController();
45
+ executionId;
46
+ io;
47
+ opening = false;
48
+ ended = false;
49
+ error;
50
+ pendingRead = false;
51
+ writeTail = Promise.resolve();
52
+ queuedWriteBytes = 0;
53
+ closing = Promise.resolve();
54
+ factoryReceipt = Promise.resolve();
55
+ constructor(options) {
56
+ capacity(options.acpMaxMessageBytes);
57
+ if (typeof options.bindingId !== 'string' || !options.bindingId)
58
+ throw need('EMPLOYEE_EXEC_IDENTITY_MISMATCH');
59
+ this.options = options;
60
+ }
61
+ parse(params) {
62
+ if (!object(params) || Object.keys(params).some(key => !['version', 'bindingId', 'operation', 'executionId', 'message'].includes(key)) || params.version !== 1 || typeof params.bindingId !== 'string' || !['open', 'write', 'read', 'close'].includes(params.operation))
63
+ throw need('EMPLOYEE_EXEC_PROTOCOL_INVALID');
64
+ if (params.bindingId !== this.options.bindingId)
65
+ throw need('EMPLOYEE_EXEC_IDENTITY_MISMATCH');
66
+ if (params.operation === 'open') {
67
+ if (Object.hasOwn(params, 'executionId') || Object.hasOwn(params, 'message'))
68
+ throw need('EMPLOYEE_EXEC_PROTOCOL_INVALID');
69
+ }
70
+ else {
71
+ if (typeof params.executionId !== 'string' || !params.executionId)
72
+ throw need('EMPLOYEE_EXEC_PROTOCOL_INVALID');
73
+ if (params.executionId !== this.executionId)
74
+ throw need('EMPLOYEE_EXEC_IDENTITY_MISMATCH');
75
+ if (params.operation === 'write' ? !object(params.message) : Object.hasOwn(params, 'message'))
76
+ throw need('EMPLOYEE_EXEC_PROTOCOL_INVALID');
77
+ }
78
+ return params;
79
+ }
80
+ active() { if (this.ended)
81
+ throw this.error ?? need('EMPLOYEE_EXEC_CLOSED'); }
82
+ async gate(signal) { this.active(); cancelled(signal); await waiting(this.options.validate(signal), signal); this.active(); cancelled(signal); }
83
+ stop(error) {
84
+ if (this.ended)
85
+ return;
86
+ this.ended = true;
87
+ this.error = error;
88
+ this.lifetime.abort();
89
+ const io = this.io;
90
+ this.io = undefined;
91
+ if (io)
92
+ this.closeTransport(io);
93
+ }
94
+ closeTransport(io) {
95
+ let closing;
96
+ try {
97
+ closing = Promise.resolve(io.close());
98
+ }
99
+ catch {
100
+ closing = Promise.reject(need('EMPLOYEE_EXEC_UNKNOWN'));
101
+ }
102
+ this.closing = Promise.all([this.closing, closing]).then(() => { }, () => { const unknown = need('EMPLOYEE_EXEC_UNKNOWN'); this.error ??= unknown; throw unknown; });
103
+ // Revocation starts cleanup without a caller waiting yet; retain rejection for close/dispose.
104
+ void this.closing.catch(() => { });
105
+ }
106
+ invalidate(error = need('EMPLOYEE_CONTEXT_CHANGED')) { this.stop(error); }
107
+ async cleanup() { await this.factoryReceipt; await this.closing; }
108
+ async dispose() { this.stop(); await this.cleanup(); }
109
+ async handle(params, signal) {
110
+ const request = this.parse(params);
111
+ cancelled(signal);
112
+ if (request.operation === 'close') {
113
+ this.stop();
114
+ await this.cleanup();
115
+ return { version: 1, closed: true, result: 'UNKNOWN' };
116
+ }
117
+ this.active();
118
+ const current = signal ? AbortSignal.any([signal, this.lifetime.signal]) : this.lifetime.signal;
119
+ if (request.operation === 'open')
120
+ return this.open(current);
121
+ if (request.operation === 'read')
122
+ return this.read(current);
123
+ const value = encoded(request.message, this.options.acpMaxMessageBytes);
124
+ const size = value.line.byteLength;
125
+ if (this.queuedWriteBytes + size > this.options.acpMaxMessageBytes) {
126
+ const error = need('ACP_MESSAGE_TOO_LARGE');
127
+ this.stop(error);
128
+ throw error;
129
+ }
130
+ this.queuedWriteBytes += size;
131
+ const operation = this.writeTail.then(async () => {
132
+ try {
133
+ await this.gate(current);
134
+ const io = this.io;
135
+ if (!io)
136
+ throw need('EMPLOYEE_EXEC_CLOSED');
137
+ await waiting(io.write(value.message, current), current);
138
+ await this.gate(current);
139
+ return { version: 1 };
140
+ }
141
+ catch (error) {
142
+ const known = this.error ?? failure(error);
143
+ this.stop(known);
144
+ throw known;
145
+ }
146
+ finally {
147
+ this.queuedWriteBytes -= size;
148
+ }
149
+ });
150
+ this.writeTail = operation.catch(() => { });
151
+ return operation;
152
+ }
153
+ async open(signal) {
154
+ if (this.opening || this.executionId)
155
+ throw need('EMPLOYEE_EXEC_ALREADY_OPEN');
156
+ this.opening = true;
157
+ try {
158
+ await this.gate(signal);
159
+ // A factory may finish after revoke/abort. Its late executor belongs to this binding and must close.
160
+ const created = Promise.resolve().then(() => { cancelled(signal); return this.options.transport(signal); }).then(async (io) => { if (this.ended || signal.aborted) {
161
+ this.closeTransport(io);
162
+ await this.closing;
163
+ throw this.error ?? need('ABORTED');
164
+ } this.io = io; return io; });
165
+ // Open cancellation may return immediately; the runtime still owns the full allocation receipt.
166
+ this.factoryReceipt = created.then(() => { }, error => { if (failure(error).code === 'EMPLOYEE_EXEC_UNKNOWN') {
167
+ const unknown = need('EMPLOYEE_EXEC_UNKNOWN');
168
+ this.error ??= unknown;
169
+ throw unknown;
170
+ } });
171
+ void this.factoryReceipt.catch(() => { });
172
+ await waiting(created, signal);
173
+ await this.gate(signal);
174
+ this.executionId = randomUUID();
175
+ return { version: 1, executionId: this.executionId };
176
+ }
177
+ catch (error) {
178
+ const known = this.error ?? failure(error);
179
+ this.stop(known);
180
+ throw known;
181
+ }
182
+ finally {
183
+ this.opening = false;
184
+ }
185
+ }
186
+ async read(signal) {
187
+ if (this.pendingRead)
188
+ throw need('EMPLOYEE_EXEC_READ_BUSY');
189
+ this.pendingRead = true;
190
+ let consumed = false;
191
+ try {
192
+ await this.gate(signal);
193
+ const io = this.io;
194
+ if (!io)
195
+ throw need('EMPLOYEE_EXEC_CLOSED');
196
+ const message = await waiting(io.read(signal).then(message => { consumed = message !== null; return message; }), signal);
197
+ if (this.ended) {
198
+ if (this.error)
199
+ throw this.error;
200
+ return { version: 1, messages: [], closed: true };
201
+ }
202
+ await this.gate(signal);
203
+ if (message === null) {
204
+ this.stop();
205
+ return { version: 1, messages: [], closed: true };
206
+ }
207
+ const value = encoded(message, this.options.acpMaxMessageBytes);
208
+ if (Buffer.byteLength(JSON.stringify({ version: 1, messages: [value.message], closed: false })) > this.options.acpMaxMessageBytes)
209
+ throw need('ACP_MESSAGE_TOO_LARGE');
210
+ return { version: 1, messages: [value.message], closed: false };
211
+ }
212
+ catch (error) {
213
+ if (this.error)
214
+ throw this.error;
215
+ if (signal.aborted && !this.ended) {
216
+ if (consumed) {
217
+ const unknown = need('EMPLOYEE_EXEC_UNKNOWN');
218
+ this.stop(unknown);
219
+ throw unknown;
220
+ }
221
+ throw need('ABORTED');
222
+ }
223
+ if (this.ended && !this.error)
224
+ return { version: 1, messages: [], closed: true };
225
+ const known = failure(error);
226
+ this.stop(known);
227
+ throw known;
228
+ }
229
+ finally {
230
+ this.pendingRead = false;
231
+ }
232
+ }
233
+ }
234
+ /** NDJSON carrier with byte bounds and backpressure derived solely from acpMaxMessageBytes. */
235
+ export function createEmployeeProcessTransport(child, acpMaxMessageBytes, stderrCodes = []) {
236
+ capacity(acpMaxMessageBytes);
237
+ let partial = Buffer.alloc(0), queuedBytes = 0, writingBytes = 0, closed = false, reason;
238
+ let processClosed = false, terminationRequested = false;
239
+ const receipt = Promise.withResolvers();
240
+ void receipt.promise.catch(() => { });
241
+ let sawOutput = false, diagnosticCode, stderrPosition = 0;
242
+ const knownCodes = stderrCodes.filter(code => /^[A-Z][A-Z0-9_]*$/.test(code));
243
+ let stderrCandidates = knownCodes;
244
+ const queue = [];
245
+ let pending;
246
+ const writes = new Set();
247
+ const settle = () => { if (!pending)
248
+ return; const reader = pending; pending = undefined; reader.clear(); reason ? reader.reject(reason) : reader.resolve(null); };
249
+ const terminate = () => {
250
+ if (processClosed || terminationRequested)
251
+ return;
252
+ terminationRequested = true;
253
+ try {
254
+ child.stdin.end();
255
+ }
256
+ catch {
257
+ receipt.reject(need('EMPLOYEE_EXEC_UNKNOWN'));
258
+ }
259
+ // A paused output pipe must drain before Node can deliver the actual child close event.
260
+ child.stdout.resume();
261
+ child.stderr.resume();
262
+ try {
263
+ if (!child.kill('SIGTERM') && !processClosed)
264
+ receipt.reject(need('EMPLOYEE_EXEC_UNKNOWN'));
265
+ }
266
+ catch {
267
+ receipt.reject(need('EMPLOYEE_EXEC_UNKNOWN'));
268
+ }
269
+ };
270
+ const finish = (error, kill = false) => {
271
+ if (!closed) {
272
+ closed = true;
273
+ reason = error;
274
+ partial = Buffer.alloc(0);
275
+ if (error) {
276
+ queue.length = 0;
277
+ queuedBytes = 0;
278
+ }
279
+ settle();
280
+ for (const reject of writes)
281
+ reject(error ?? need('EMPLOYEE_EXEC_UNKNOWN'));
282
+ writes.clear();
283
+ }
284
+ if (kill)
285
+ terminate();
286
+ };
287
+ const output = (message, bytes) => {
288
+ if (pending) {
289
+ const reader = pending;
290
+ pending = undefined;
291
+ reader.clear();
292
+ reader.resolve(message);
293
+ }
294
+ else {
295
+ if (queuedBytes + bytes > acpMaxMessageBytes)
296
+ throw need('ACP_MESSAGE_TOO_LARGE');
297
+ queue.push({ message, bytes });
298
+ queuedBytes += bytes;
299
+ child.stdout.pause();
300
+ }
301
+ };
302
+ child.stdout.on('data', (chunk) => {
303
+ if (closed)
304
+ return;
305
+ sawOutput = true;
306
+ diagnosticCode = undefined;
307
+ try {
308
+ let start = 0;
309
+ while (start < chunk.length) {
310
+ const end = chunk.indexOf(10, start), last = end < 0 ? chunk.length : end;
311
+ const length = partial.length + last - start;
312
+ if (length > acpMaxMessageBytes || queuedBytes + length > acpMaxMessageBytes)
313
+ throw need('ACP_MESSAGE_TOO_LARGE');
314
+ if (end < 0) {
315
+ partial = Buffer.concat([partial, chunk.subarray(start)]);
316
+ break;
317
+ }
318
+ const line = partial.length ? Buffer.concat([partial, chunk.subarray(start, end)]) : chunk.subarray(start, end);
319
+ partial = Buffer.alloc(0);
320
+ start = end + 1;
321
+ let value;
322
+ try {
323
+ value = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(line));
324
+ }
325
+ catch {
326
+ throw need('EMPLOYEE_EXEC_PROTOCOL_INVALID');
327
+ }
328
+ if (!object(value))
329
+ throw need('EMPLOYEE_EXEC_PROTOCOL_INVALID');
330
+ output(value, line.byteLength);
331
+ }
332
+ }
333
+ catch (error) {
334
+ finish(failure(error), true);
335
+ }
336
+ });
337
+ // Match fixed startup codes byte by byte. No arbitrary diagnostic bytes are retained.
338
+ child.stderr.on('data', (chunk) => {
339
+ if (sawOutput || closed || !knownCodes.length)
340
+ return;
341
+ for (const byte of chunk) {
342
+ if (byte === 10) {
343
+ const match = stderrCandidates.find(code => code.length === stderrPosition);
344
+ if (match)
345
+ diagnosticCode = match;
346
+ stderrPosition = 0;
347
+ stderrCandidates = knownCodes;
348
+ }
349
+ else if (stderrCandidates.length) {
350
+ stderrCandidates = stderrCandidates.filter(code => code.charCodeAt(stderrPosition) === byte);
351
+ stderrPosition++;
352
+ }
353
+ }
354
+ });
355
+ child.stderr.resume();
356
+ child.stdin.on('error', () => finish(need(diagnosticCode ?? 'EMPLOYEE_EXEC_UNKNOWN'), true));
357
+ child.once('error', () => finish(need('EMPLOYEE_EXEC_UNKNOWN'), true));
358
+ child.once('close', (code, signal) => { processClosed = true; finish(partial.length ? need('EMPLOYEE_EXEC_PROTOCOL_INVALID') : code === 0 && !signal ? undefined : need(diagnosticCode ?? 'EMPLOYEE_EXEC_UNKNOWN')); receipt.resolve(); });
359
+ return {
360
+ write: async (message, signal) => {
361
+ cancelled(signal);
362
+ if (closed)
363
+ throw reason ?? need('EMPLOYEE_EXEC_UNKNOWN');
364
+ const value = encoded(message, acpMaxMessageBytes);
365
+ if (writingBytes + value.line.byteLength > acpMaxMessageBytes) {
366
+ const error = need('ACP_MESSAGE_TOO_LARGE');
367
+ finish(error, true);
368
+ throw error;
369
+ }
370
+ writingBytes += value.line.byteLength;
371
+ await new Promise((resolve, reject) => {
372
+ let done = false;
373
+ const cleanup = () => { if (done)
374
+ return false; done = true; writingBytes -= value.line.byteLength; writes.delete(fail); signal?.removeEventListener('abort', abort); return true; };
375
+ const fail = (error) => { if (cleanup())
376
+ reject(error); };
377
+ const abort = () => { const error = need('EMPLOYEE_EXEC_UNKNOWN'); finish(error, true); fail(error); };
378
+ writes.add(fail);
379
+ signal?.addEventListener('abort', abort, { once: true });
380
+ try {
381
+ child.stdin.write(value.line, error => { if (error) {
382
+ finish(need('EMPLOYEE_EXEC_UNKNOWN'), true);
383
+ fail(need('EMPLOYEE_EXEC_UNKNOWN'));
384
+ }
385
+ else if (cleanup())
386
+ resolve(); });
387
+ }
388
+ catch {
389
+ finish(need('EMPLOYEE_EXEC_UNKNOWN'), true);
390
+ fail(need('EMPLOYEE_EXEC_UNKNOWN'));
391
+ }
392
+ if (signal?.aborted)
393
+ abort();
394
+ });
395
+ },
396
+ read: async (signal) => {
397
+ cancelled(signal);
398
+ if (reason)
399
+ throw reason;
400
+ if (pending)
401
+ throw need('EMPLOYEE_EXEC_READ_BUSY');
402
+ if (queue.length) {
403
+ const item = queue.shift();
404
+ queuedBytes -= item.bytes;
405
+ if (!queue.length && !closed)
406
+ child.stdout.resume();
407
+ return item.message;
408
+ }
409
+ if (closed)
410
+ return null;
411
+ return new Promise((resolve, reject) => {
412
+ const abort = () => { if (pending?.resolve !== resolve)
413
+ return; pending.clear(); pending = undefined; reject(need('ABORTED')); };
414
+ pending = { resolve, reject, clear: () => signal?.removeEventListener('abort', abort) };
415
+ signal?.addEventListener('abort', abort, { once: true });
416
+ child.stdout.resume();
417
+ if (signal?.aborted)
418
+ abort();
419
+ });
420
+ },
421
+ close: () => { finish(undefined, true); queue.length = 0; queuedBytes = 0; return receipt.promise; }
422
+ };
423
+ }
@@ -0,0 +1,66 @@
1
+ import type { EmployeeEndpointTarget, NetworkApi } from '@ai-workbench/network/api';
2
+ export interface EmployeeLocationSource {
3
+ getWorkspaceLocationState(): {
4
+ instanceId: string;
5
+ epoch: number;
6
+ phase: string;
7
+ items: {
8
+ workspaceId: string;
9
+ bindingId: string | null;
10
+ source: string | null;
11
+ status: string;
12
+ }[];
13
+ };
14
+ getLocalWorkspaceContext(request: {
15
+ instanceId: string;
16
+ epoch: number;
17
+ workspaceId: string;
18
+ expectedBindingId: string;
19
+ }): Promise<{
20
+ ok: true;
21
+ value: {
22
+ version: 1;
23
+ instanceId: string;
24
+ epoch: number;
25
+ workspaceId: string;
26
+ bindingId: string;
27
+ source: 'local';
28
+ directory: {
29
+ path: string;
30
+ device: number;
31
+ inode: number;
32
+ };
33
+ };
34
+ } | {
35
+ ok: false;
36
+ error: {
37
+ code: string;
38
+ message: string;
39
+ requires: string;
40
+ };
41
+ }>;
42
+ }
43
+ export type EmployeeEndpointSource = Pick<NetworkApi, 'prepareEmployeeEndpoint'>;
44
+ export interface EmployeeLocationOptions {
45
+ api: EmployeeLocationSource;
46
+ network: EmployeeEndpointSource;
47
+ workspace: {
48
+ id: string;
49
+ path: string;
50
+ };
51
+ connection: {
52
+ instanceId: string;
53
+ epoch: number;
54
+ };
55
+ expectedLocal?: EmployeeEndpointTarget['local'];
56
+ settingsPath: string;
57
+ signal?: AbortSignal;
58
+ }
59
+ /** One admission binds source/canonical directory and real N/S execution identity. No host-path substitute. */
60
+ export declare function admitEmployeeLocation(options: EmployeeLocationOptions): Promise<{
61
+ cwd: string;
62
+ bindingId: string;
63
+ workspaceId: string;
64
+ endpoint: EmployeeEndpointTarget;
65
+ validate: (signal: AbortSignal) => Promise<void>;
66
+ }>;
@@ -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>;