dsh-ops 0.0.0-stage → 0.2.1

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 (139) hide show
  1. package/CHANGELOG.md +189 -0
  2. package/LICENSE +30 -0
  3. package/NOTICE +106 -0
  4. package/PROVENANCE.md +417 -0
  5. package/README.en.md +121 -0
  6. package/README.md +113 -2
  7. package/README.zh.md +114 -0
  8. package/bin/dsh-ops.mjs +1216 -0
  9. package/cordis.patch.yml +160 -0
  10. package/docs/manual-validation.md +53 -0
  11. package/docs/schema-baseline.json +64 -0
  12. package/docs/schema-current.json +84 -0
  13. package/docs/schema-measurement.md +17 -0
  14. package/dsh-plugin.json +88 -0
  15. package/icon.svg +12 -0
  16. package/lib/binary.js +409 -0
  17. package/lib/config.js +198 -0
  18. package/lib/handshake.js +252 -0
  19. package/lib/index.js +108 -0
  20. package/lib/jobs.js +42 -0
  21. package/lib/policy.js +64 -0
  22. package/lib/presentation.js +63 -0
  23. package/lib/profile-install.js +61 -0
  24. package/lib/rust.js +194 -0
  25. package/lib/session-shells.js +78 -0
  26. package/lib/shells.js +993 -0
  27. package/lib/tools.js +657 -0
  28. package/locale/en.json +6 -0
  29. package/locale/zh.json +6 -0
  30. package/package.json +114 -4
  31. package/vendor/fastctx/Cargo.lock +3210 -0
  32. package/vendor/fastctx/Cargo.toml +94 -0
  33. package/vendor/fastctx/FORK.md +119 -0
  34. package/vendor/fastctx/LICENSE-APACHE +201 -0
  35. package/vendor/fastctx/NOTICE +40 -0
  36. package/vendor/fastctx/README.md +439 -0
  37. package/vendor/fastctx/THIRD_PARTY_LICENSES.md +17 -0
  38. package/vendor/fastctx/THIRD_PARTY_LICENSES_RUST.md +7914 -0
  39. package/vendor/fastctx/UPSTREAM.md +49 -0
  40. package/vendor/fastctx/build.rs +413 -0
  41. package/vendor/fastctx/src/background_status.rs +403 -0
  42. package/vendor/fastctx/src/binary.rs +75 -0
  43. package/vendor/fastctx/src/bounded_sort.rs +500 -0
  44. package/vendor/fastctx/src/budget.rs +781 -0
  45. package/vendor/fastctx/src/cli/mod.rs +110 -0
  46. package/vendor/fastctx/src/context_guard.rs +289 -0
  47. package/vendor/fastctx/src/control/mod.rs +6 -0
  48. package/vendor/fastctx/src/control/paths.rs +49 -0
  49. package/vendor/fastctx/src/control/settings.rs +753 -0
  50. package/vendor/fastctx/src/control/transaction.rs +531 -0
  51. package/vendor/fastctx/src/edit/document.rs +535 -0
  52. package/vendor/fastctx/src/edit/locks.rs +371 -0
  53. package/vendor/fastctx/src/edit/mod.rs +213 -0
  54. package/vendor/fastctx/src/edit/private_storage/unix.rs +315 -0
  55. package/vendor/fastctx/src/edit/private_storage/windows.rs +793 -0
  56. package/vendor/fastctx/src/edit/private_storage.rs +234 -0
  57. package/vendor/fastctx/src/edit/replace.rs +1030 -0
  58. package/vendor/fastctx/src/edit_server.rs +53 -0
  59. package/vendor/fastctx/src/encoding/reference_v011.rs +587 -0
  60. package/vendor/fastctx/src/encoding/snapshot_pipeline.rs +1678 -0
  61. package/vendor/fastctx/src/encoding.rs +1118 -0
  62. package/vendor/fastctx/src/file_executor.rs +1151 -0
  63. package/vendor/fastctx/src/file_snapshot.rs +1491 -0
  64. package/vendor/fastctx/src/glob_filter.rs +98 -0
  65. package/vendor/fastctx/src/glob_tool.rs +653 -0
  66. package/vendor/fastctx/src/grep_sink.rs +1162 -0
  67. package/vendor/fastctx/src/grep_tool.rs +2449 -0
  68. package/vendor/fastctx/src/lib.rs +45 -0
  69. package/vendor/fastctx/src/main.rs +15 -0
  70. package/vendor/fastctx/src/model.rs +51 -0
  71. package/vendor/fastctx/src/model_guidance.rs +62 -0
  72. package/vendor/fastctx/src/operation.rs +356 -0
  73. package/vendor/fastctx/src/ordered_window.rs +1235 -0
  74. package/vendor/fastctx/src/os_environment.rs +414 -0
  75. package/vendor/fastctx/src/path_codec.rs +850 -0
  76. package/vendor/fastctx/src/paths.rs +244 -0
  77. package/vendor/fastctx/src/process_identity.rs +763 -0
  78. package/vendor/fastctx/src/process_policy.rs +74 -0
  79. package/vendor/fastctx/src/read_tool/batch.rs +496 -0
  80. package/vendor/fastctx/src/read_tool/hex_file.rs +141 -0
  81. package/vendor/fastctx/src/read_tool/image_file.rs +88 -0
  82. package/vendor/fastctx/src/read_tool/mod.rs +245 -0
  83. package/vendor/fastctx/src/read_tool/pdf.rs +470 -0
  84. package/vendor/fastctx/src/read_tool/pdf_disabled.rs +47 -0
  85. package/vendor/fastctx/src/read_tool/pdf_engine.rs +664 -0
  86. package/vendor/fastctx/src/read_tool/text_file.rs +351 -0
  87. package/vendor/fastctx/src/render_plan.rs +468 -0
  88. package/vendor/fastctx/src/runtime/activity.rs +159 -0
  89. package/vendor/fastctx/src/runtime/hosts.rs +99 -0
  90. package/vendor/fastctx/src/runtime/journal.rs +556 -0
  91. package/vendor/fastctx/src/runtime/local_ipc.rs +186 -0
  92. package/vendor/fastctx/src/runtime/mod.rs +746 -0
  93. package/vendor/fastctx/src/runtime/protocol.rs +296 -0
  94. package/vendor/fastctx/src/runtime/session.rs +536 -0
  95. package/vendor/fastctx/src/runtime/windows_process.rs +66 -0
  96. package/vendor/fastctx/src/search_parallelism.rs +106 -0
  97. package/vendor/fastctx/src/search_text.rs +227 -0
  98. package/vendor/fastctx/src/server.rs +359 -0
  99. package/vendor/fastctx/src/server_manifest.rs +468 -0
  100. package/vendor/fastctx/src/server_support.rs +826 -0
  101. package/vendor/fastctx/src/session.rs +629 -0
  102. package/vendor/fastctx/src/shell/apply_patch_hint.rs +41 -0
  103. package/vendor/fastctx/src/shell/bash.rs +263 -0
  104. package/vendor/fastctx/src/shell/buffer.rs +108 -0
  105. package/vendor/fastctx/src/shell/encoding.rs +403 -0
  106. package/vendor/fastctx/src/shell/foreground.rs +115 -0
  107. package/vendor/fastctx/src/shell/jobs/admission.rs +91 -0
  108. package/vendor/fastctx/src/shell/jobs/background.rs +146 -0
  109. package/vendor/fastctx/src/shell/jobs/host.rs +830 -0
  110. package/vendor/fastctx/src/shell/jobs/identity.rs +29 -0
  111. package/vendor/fastctx/src/shell/jobs/mod.rs +1513 -0
  112. package/vendor/fastctx/src/shell/jobs/model.rs +244 -0
  113. package/vendor/fastctx/src/shell/jobs/output_log.rs +1148 -0
  114. package/vendor/fastctx/src/shell/jobs/store.rs +1300 -0
  115. package/vendor/fastctx/src/shell/mod.rs +345 -0
  116. package/vendor/fastctx/src/shell/normalize.rs +389 -0
  117. package/vendor/fastctx/src/shell/output.rs +406 -0
  118. package/vendor/fastctx/src/shell/process.rs +493 -0
  119. package/vendor/fastctx/src/shell_server.rs +156 -0
  120. package/vendor/fastctx/src/skip_report.rs +83 -0
  121. package/vendor/fastctx/src/stdio_transport.rs +177 -0
  122. package/vendor/fastctx/src/tool_schema.rs +204 -0
  123. package/vendor/fastctx/src/traversal.rs +846 -0
  124. package/vendor/fastctx/third-party/pdfium-7763/LICENSE +9 -0
  125. package/vendor/fastctx/third-party/pdfium-7763/licenses/abseil.txt +202 -0
  126. package/vendor/fastctx/third-party/pdfium-7763/licenses/agg23.txt +14 -0
  127. package/vendor/fastctx/third-party/pdfium-7763/licenses/fast_float.txt +27 -0
  128. package/vendor/fastctx/third-party/pdfium-7763/licenses/freetype.txt +169 -0
  129. package/vendor/fastctx/third-party/pdfium-7763/licenses/icu.txt +542 -0
  130. package/vendor/fastctx/third-party/pdfium-7763/licenses/lcms.txt +27 -0
  131. package/vendor/fastctx/third-party/pdfium-7763/licenses/libjpeg_turbo.ijg +260 -0
  132. package/vendor/fastctx/third-party/pdfium-7763/licenses/libjpeg_turbo.md +135 -0
  133. package/vendor/fastctx/third-party/pdfium-7763/licenses/libopenjpeg.txt +32 -0
  134. package/vendor/fastctx/third-party/pdfium-7763/licenses/libpng.txt +134 -0
  135. package/vendor/fastctx/third-party/pdfium-7763/licenses/libtiff.txt +21 -0
  136. package/vendor/fastctx/third-party/pdfium-7763/licenses/llvm-libc.txt +278 -0
  137. package/vendor/fastctx/third-party/pdfium-7763/licenses/pdfium.txt +230 -0
  138. package/vendor/fastctx/third-party/pdfium-7763/licenses/simdutf.txt +18 -0
  139. package/vendor/fastctx/third-party/pdfium-7763/licenses/zlib.txt +29 -0
@@ -0,0 +1,296 @@
1
+ //! Length-delimited control-center handshake, framed requests, and a raw MCP answer stream.
2
+
3
+ use crate::process_identity::ProcessIdentity;
4
+ use crate::server::ServerOptions;
5
+ use crate::session::SessionEnvironment;
6
+ use serde::{Deserialize, Serialize};
7
+ use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
8
+
9
+ const PROTOCOL_VERSION: u32 = 2;
10
+ const MAX_HANDSHAKE_BYTES: usize = 16 * 1024 * 1024;
11
+ /// Largest payload the proxy places in one request frame, and the buffer the control center reads
12
+ /// it back into.
13
+ pub(crate) const MAX_REQUEST_FRAME_BYTES: usize = 64 * 1024;
14
+
15
+ #[derive(Debug, Deserialize, Serialize)]
16
+ pub(crate) struct Handshake {
17
+ protocol_version: u32,
18
+ pub(crate) options: ServerOptions,
19
+ pub(crate) environment: SessionEnvironment,
20
+ /// The host application this session belongs to, when the proxy could identify it. The control
21
+ /// center keeps its runtime warm while any named host is still running.
22
+ pub(crate) host: Option<ProcessIdentity>,
23
+ }
24
+
25
+ impl Handshake {
26
+ pub(crate) fn new(
27
+ options: ServerOptions,
28
+ environment: SessionEnvironment,
29
+ host: Option<ProcessIdentity>,
30
+ ) -> Self {
31
+ Self {
32
+ protocol_version: PROTOCOL_VERSION,
33
+ options,
34
+ environment,
35
+ host,
36
+ }
37
+ }
38
+
39
+ pub(crate) fn validate(&self) -> Result<(), String> {
40
+ if self.protocol_version != PROTOCOL_VERSION {
41
+ return Err(format!(
42
+ "Unsupported control-center protocol version {}; expected {}.",
43
+ self.protocol_version, PROTOCOL_VERSION
44
+ ));
45
+ }
46
+ if !self.environment.cwd().is_absolute() {
47
+ return Err(
48
+ "The control-center handshake contained a non-absolute working directory."
49
+ .to_string(),
50
+ );
51
+ }
52
+ Ok(())
53
+ }
54
+ }
55
+
56
+ #[derive(Debug, Deserialize, Serialize)]
57
+ struct HandshakeResponse {
58
+ error: Option<String>,
59
+ }
60
+
61
+ pub(crate) async fn write_handshake(
62
+ stream: &mut (impl AsyncWrite + Unpin),
63
+ handshake: &Handshake,
64
+ ) -> Result<(), String> {
65
+ write_frame(stream, handshake, "control-center handshake").await
66
+ }
67
+
68
+ pub(crate) async fn read_handshake(
69
+ stream: &mut (impl AsyncRead + Unpin),
70
+ ) -> Result<Handshake, String> {
71
+ let handshake: Handshake = read_frame(stream, "control-center handshake").await?;
72
+ handshake.validate()?;
73
+ Ok(handshake)
74
+ }
75
+
76
+ pub(crate) async fn write_handshake_success(
77
+ stream: &mut (impl AsyncWrite + Unpin),
78
+ ) -> Result<(), String> {
79
+ write_frame(
80
+ stream,
81
+ &HandshakeResponse { error: None },
82
+ "control-center handshake response",
83
+ )
84
+ .await
85
+ }
86
+
87
+ pub(crate) async fn write_handshake_error(
88
+ stream: &mut (impl AsyncWrite + Unpin),
89
+ error: String,
90
+ ) -> Result<(), String> {
91
+ write_frame(
92
+ stream,
93
+ &HandshakeResponse { error: Some(error) },
94
+ "control-center handshake response",
95
+ )
96
+ .await
97
+ }
98
+
99
+ pub(crate) async fn read_handshake_response(
100
+ stream: &mut (impl AsyncRead + Unpin),
101
+ ) -> Result<(), String> {
102
+ let response: HandshakeResponse =
103
+ read_frame(stream, "control-center handshake response").await?;
104
+ match response.error {
105
+ Some(error) => Err(error),
106
+ None => Ok(()),
107
+ }
108
+ }
109
+
110
+ /// Sends one chunk of MCP request bytes to the control center as a length-prefixed frame.
111
+ ///
112
+ /// A raw byte stream cannot say "no more requests" on every platform: Unix half-closes a socket,
113
+ /// Windows named pipes have no equivalent. Without that mark the control center holds a finished
114
+ /// session open, and a proxy whose stdin closed would have to guess how long to wait for the
115
+ /// answers it is still owed — a guess that is either too short to deliver them or too long to
116
+ /// shut down promptly. The explicit end-of-input frame removes the guess. Both ends always come
117
+ /// from the same build, because the endpoint name carries the build id, so the framing needs no
118
+ /// negotiation.
119
+ pub(crate) async fn write_request_frame(
120
+ output: &mut (impl AsyncWrite + Unpin),
121
+ payload: &[u8],
122
+ ) -> Result<(), FrameWriteFailure> {
123
+ let mut reached_engine = false;
124
+ for chunk in payload.chunks(MAX_REQUEST_FRAME_BYTES) {
125
+ let length =
126
+ u32::try_from(chunk.len()).expect("a request frame never exceeds the frame size limit");
127
+ if let Err(error) = output.write_all(&length.to_be_bytes()).await {
128
+ return Err(FrameWriteFailure {
129
+ reached_engine,
130
+ error,
131
+ });
132
+ }
133
+ if let Err(error) = output.write_all(chunk).await {
134
+ // The length prefix landed, so the engine saw part of this payload.
135
+ return Err(FrameWriteFailure {
136
+ reached_engine: true,
137
+ error,
138
+ });
139
+ }
140
+ reached_engine = true;
141
+ }
142
+ output.flush().await.map_err(|error| FrameWriteFailure {
143
+ reached_engine,
144
+ error,
145
+ })
146
+ }
147
+
148
+ /// A frame write that failed, and whether any of its bytes reached the engine.
149
+ ///
150
+ /// The distinction is what lets a closed-out call say "nothing ran" instead of the weaker "this
151
+ /// may or may not have finished", which matters most for the calls that have side effects.
152
+ pub(crate) struct FrameWriteFailure {
153
+ pub(crate) reached_engine: bool,
154
+ pub(crate) error: std::io::Error,
155
+ }
156
+
157
+ /// Marks the end of input, which is what makes the control center stop expecting requests.
158
+ pub(crate) async fn write_end_of_input(
159
+ output: &mut (impl AsyncWrite + Unpin),
160
+ ) -> std::io::Result<()> {
161
+ output.write_all(&0_u32.to_be_bytes()).await?;
162
+ output.flush().await
163
+ }
164
+
165
+ /// Reassembles framed requests into the plain byte stream the MCP server reads.
166
+ ///
167
+ /// Returns once the proxy marks the end of input, which is what makes the server observe EOF and
168
+ /// end the session.
169
+ pub(crate) async fn receive_requests(
170
+ input: &mut (impl AsyncRead + Unpin),
171
+ output: &mut (impl AsyncWrite + Unpin),
172
+ ) -> Result<(), String> {
173
+ let mut header = [0_u8; 4];
174
+ let mut payload = vec![0_u8; MAX_REQUEST_FRAME_BYTES];
175
+ loop {
176
+ match input.read_exact(&mut header).await {
177
+ Ok(_) => {}
178
+ // A proxy that died without marking the end of input tells the server the same thing:
179
+ // nothing more is coming.
180
+ Err(error) if error.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(()),
181
+ Err(error) => return Err(format!("Cannot read the request frame length: {error}")),
182
+ }
183
+ let length = u32::from_be_bytes(header) as usize;
184
+ if length == 0 {
185
+ return Ok(());
186
+ }
187
+ if length > MAX_REQUEST_FRAME_BYTES {
188
+ return Err(format!(
189
+ "A {length}-byte request frame exceeds the {MAX_REQUEST_FRAME_BYTES}-byte limit."
190
+ ));
191
+ }
192
+ input
193
+ .read_exact(&mut payload[..length])
194
+ .await
195
+ .map_err(|error| format!("Cannot read a {length}-byte request frame: {error}"))?;
196
+ output
197
+ .write_all(&payload[..length])
198
+ .await
199
+ .map_err(|error| format!("Cannot hand a request frame to the MCP server: {error}"))?;
200
+ output
201
+ .flush()
202
+ .await
203
+ .map_err(|error| format!("Cannot flush a request frame to the MCP server: {error}"))?;
204
+ }
205
+ }
206
+
207
+ async fn write_frame<T: Serialize>(
208
+ stream: &mut (impl AsyncWrite + Unpin),
209
+ value: &T,
210
+ label: &str,
211
+ ) -> Result<(), String> {
212
+ let bytes =
213
+ serde_json::to_vec(value).map_err(|error| format!("Cannot encode the {label}: {error}"))?;
214
+ if bytes.len() > MAX_HANDSHAKE_BYTES {
215
+ return Err(format!(
216
+ "Cannot send the {label}: {} bytes exceeds the {}-byte safety limit.",
217
+ bytes.len(),
218
+ MAX_HANDSHAKE_BYTES
219
+ ));
220
+ }
221
+ let length = u32::try_from(bytes.len())
222
+ .map_err(|_| format!("Cannot send the {label}: its length cannot be represented."))?;
223
+ stream
224
+ .write_all(&length.to_be_bytes())
225
+ .await
226
+ .map_err(|error| format!("Cannot write the {label} length: {error}"))?;
227
+ stream
228
+ .write_all(&bytes)
229
+ .await
230
+ .map_err(|error| format!("Cannot write the {label} body: {error}"))?;
231
+ stream
232
+ .flush()
233
+ .await
234
+ .map_err(|error| format!("Cannot flush the {label}: {error}"))
235
+ }
236
+
237
+ async fn read_frame<T: for<'de> Deserialize<'de>>(
238
+ stream: &mut (impl AsyncRead + Unpin),
239
+ label: &str,
240
+ ) -> Result<T, String> {
241
+ let mut length = [0_u8; 4];
242
+ stream
243
+ .read_exact(&mut length)
244
+ .await
245
+ .map_err(|error| format!("Cannot read the {label} length: {error}"))?;
246
+ let length = u32::from_be_bytes(length) as usize;
247
+ if length > MAX_HANDSHAKE_BYTES {
248
+ return Err(format!(
249
+ "Cannot read the {label}: {length} bytes exceeds the {MAX_HANDSHAKE_BYTES}-byte safety limit."
250
+ ));
251
+ }
252
+ let mut bytes = vec![0_u8; length];
253
+ stream
254
+ .read_exact(&mut bytes)
255
+ .await
256
+ .map_err(|error| format!("Cannot read the {label} body: {error}"))?;
257
+ serde_json::from_slice(&bytes).map_err(|error| format!("Cannot parse the {label}: {error}"))
258
+ }
259
+
260
+ #[cfg(test)]
261
+ mod tests {
262
+ use super::{
263
+ Handshake, read_handshake, read_handshake_response, write_handshake,
264
+ write_handshake_success,
265
+ };
266
+ use crate::server::ServerOptions;
267
+ use crate::session::SessionEnvironment;
268
+ use std::ffi::OsString;
269
+ use tokio::io::{AsyncReadExt, AsyncWriteExt};
270
+
271
+ #[tokio::test]
272
+ async fn handshake_is_framed_without_consuming_following_mcp_bytes() {
273
+ let (mut client, mut server) = tokio::io::duplex(64 * 1024);
274
+ let handshake = Handshake::new(
275
+ ServerOptions { enable_shell: true },
276
+ SessionEnvironment::new(
277
+ std::env::current_dir().unwrap(),
278
+ vec![(OsString::from("PATH"), OsString::from("sentinel"))],
279
+ ),
280
+ None,
281
+ );
282
+ let client_task = tokio::spawn(async move {
283
+ write_handshake(&mut client, &handshake).await.unwrap();
284
+ client.write_all(b"mcp\n").await.unwrap();
285
+ read_handshake_response(&mut client).await.unwrap();
286
+ });
287
+
288
+ let decoded = read_handshake(&mut server).await.unwrap();
289
+ assert!(decoded.options.enable_shell);
290
+ let mut tail = [0_u8; 4];
291
+ server.read_exact(&mut tail).await.unwrap();
292
+ assert_eq!(&tail, b"mcp\n");
293
+ write_handshake_success(&mut server).await.unwrap();
294
+ client_task.await.unwrap();
295
+ }
296
+ }