dsh-ops 0.0.0-stage → 0.2.2

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 (140) hide show
  1. package/CHANGELOG.md +202 -0
  2. package/LICENSE +30 -0
  3. package/NOTICE +106 -0
  4. package/PROVENANCE.md +435 -0
  5. package/README.en.md +126 -0
  6. package/README.md +115 -2
  7. package/README.zh.md +116 -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/release-0.2.1.md +72 -0
  12. package/docs/schema-baseline.json +64 -0
  13. package/docs/schema-current.json +84 -0
  14. package/docs/schema-measurement.md +17 -0
  15. package/dsh-plugin.json +88 -0
  16. package/icon.svg +12 -0
  17. package/lib/binary.js +409 -0
  18. package/lib/config.js +198 -0
  19. package/lib/handshake.js +252 -0
  20. package/lib/index.js +108 -0
  21. package/lib/jobs.js +42 -0
  22. package/lib/policy.js +64 -0
  23. package/lib/presentation.js +63 -0
  24. package/lib/profile-install.js +61 -0
  25. package/lib/rust.js +194 -0
  26. package/lib/session-shells.js +78 -0
  27. package/lib/shells.js +998 -0
  28. package/lib/tools.js +657 -0
  29. package/locale/en.json +6 -0
  30. package/locale/zh.json +6 -0
  31. package/package.json +114 -4
  32. package/vendor/fastctx/Cargo.lock +3210 -0
  33. package/vendor/fastctx/Cargo.toml +94 -0
  34. package/vendor/fastctx/FORK.md +119 -0
  35. package/vendor/fastctx/LICENSE-APACHE +201 -0
  36. package/vendor/fastctx/NOTICE +40 -0
  37. package/vendor/fastctx/README.md +439 -0
  38. package/vendor/fastctx/THIRD_PARTY_LICENSES.md +17 -0
  39. package/vendor/fastctx/THIRD_PARTY_LICENSES_RUST.md +7914 -0
  40. package/vendor/fastctx/UPSTREAM.md +49 -0
  41. package/vendor/fastctx/build.rs +413 -0
  42. package/vendor/fastctx/src/background_status.rs +403 -0
  43. package/vendor/fastctx/src/binary.rs +75 -0
  44. package/vendor/fastctx/src/bounded_sort.rs +500 -0
  45. package/vendor/fastctx/src/budget.rs +781 -0
  46. package/vendor/fastctx/src/cli/mod.rs +110 -0
  47. package/vendor/fastctx/src/context_guard.rs +289 -0
  48. package/vendor/fastctx/src/control/mod.rs +6 -0
  49. package/vendor/fastctx/src/control/paths.rs +49 -0
  50. package/vendor/fastctx/src/control/settings.rs +753 -0
  51. package/vendor/fastctx/src/control/transaction.rs +531 -0
  52. package/vendor/fastctx/src/edit/document.rs +535 -0
  53. package/vendor/fastctx/src/edit/locks.rs +371 -0
  54. package/vendor/fastctx/src/edit/mod.rs +213 -0
  55. package/vendor/fastctx/src/edit/private_storage/unix.rs +315 -0
  56. package/vendor/fastctx/src/edit/private_storage/windows.rs +793 -0
  57. package/vendor/fastctx/src/edit/private_storage.rs +234 -0
  58. package/vendor/fastctx/src/edit/replace.rs +1030 -0
  59. package/vendor/fastctx/src/edit_server.rs +53 -0
  60. package/vendor/fastctx/src/encoding/reference_v011.rs +587 -0
  61. package/vendor/fastctx/src/encoding/snapshot_pipeline.rs +1678 -0
  62. package/vendor/fastctx/src/encoding.rs +1118 -0
  63. package/vendor/fastctx/src/file_executor.rs +1151 -0
  64. package/vendor/fastctx/src/file_snapshot.rs +1491 -0
  65. package/vendor/fastctx/src/glob_filter.rs +98 -0
  66. package/vendor/fastctx/src/glob_tool.rs +653 -0
  67. package/vendor/fastctx/src/grep_sink.rs +1162 -0
  68. package/vendor/fastctx/src/grep_tool.rs +2449 -0
  69. package/vendor/fastctx/src/lib.rs +45 -0
  70. package/vendor/fastctx/src/main.rs +15 -0
  71. package/vendor/fastctx/src/model.rs +51 -0
  72. package/vendor/fastctx/src/model_guidance.rs +62 -0
  73. package/vendor/fastctx/src/operation.rs +356 -0
  74. package/vendor/fastctx/src/ordered_window.rs +1235 -0
  75. package/vendor/fastctx/src/os_environment.rs +414 -0
  76. package/vendor/fastctx/src/path_codec.rs +850 -0
  77. package/vendor/fastctx/src/paths.rs +244 -0
  78. package/vendor/fastctx/src/process_identity.rs +763 -0
  79. package/vendor/fastctx/src/process_policy.rs +74 -0
  80. package/vendor/fastctx/src/read_tool/batch.rs +496 -0
  81. package/vendor/fastctx/src/read_tool/hex_file.rs +141 -0
  82. package/vendor/fastctx/src/read_tool/image_file.rs +88 -0
  83. package/vendor/fastctx/src/read_tool/mod.rs +245 -0
  84. package/vendor/fastctx/src/read_tool/pdf.rs +470 -0
  85. package/vendor/fastctx/src/read_tool/pdf_disabled.rs +47 -0
  86. package/vendor/fastctx/src/read_tool/pdf_engine.rs +664 -0
  87. package/vendor/fastctx/src/read_tool/text_file.rs +351 -0
  88. package/vendor/fastctx/src/render_plan.rs +468 -0
  89. package/vendor/fastctx/src/runtime/activity.rs +159 -0
  90. package/vendor/fastctx/src/runtime/hosts.rs +99 -0
  91. package/vendor/fastctx/src/runtime/journal.rs +556 -0
  92. package/vendor/fastctx/src/runtime/local_ipc.rs +186 -0
  93. package/vendor/fastctx/src/runtime/mod.rs +746 -0
  94. package/vendor/fastctx/src/runtime/protocol.rs +296 -0
  95. package/vendor/fastctx/src/runtime/session.rs +536 -0
  96. package/vendor/fastctx/src/runtime/windows_process.rs +66 -0
  97. package/vendor/fastctx/src/search_parallelism.rs +106 -0
  98. package/vendor/fastctx/src/search_text.rs +227 -0
  99. package/vendor/fastctx/src/server.rs +359 -0
  100. package/vendor/fastctx/src/server_manifest.rs +468 -0
  101. package/vendor/fastctx/src/server_support.rs +826 -0
  102. package/vendor/fastctx/src/session.rs +629 -0
  103. package/vendor/fastctx/src/shell/apply_patch_hint.rs +41 -0
  104. package/vendor/fastctx/src/shell/bash.rs +263 -0
  105. package/vendor/fastctx/src/shell/buffer.rs +108 -0
  106. package/vendor/fastctx/src/shell/encoding.rs +403 -0
  107. package/vendor/fastctx/src/shell/foreground.rs +115 -0
  108. package/vendor/fastctx/src/shell/jobs/admission.rs +91 -0
  109. package/vendor/fastctx/src/shell/jobs/background.rs +146 -0
  110. package/vendor/fastctx/src/shell/jobs/host.rs +830 -0
  111. package/vendor/fastctx/src/shell/jobs/identity.rs +29 -0
  112. package/vendor/fastctx/src/shell/jobs/mod.rs +1513 -0
  113. package/vendor/fastctx/src/shell/jobs/model.rs +244 -0
  114. package/vendor/fastctx/src/shell/jobs/output_log.rs +1148 -0
  115. package/vendor/fastctx/src/shell/jobs/store.rs +1300 -0
  116. package/vendor/fastctx/src/shell/mod.rs +345 -0
  117. package/vendor/fastctx/src/shell/normalize.rs +389 -0
  118. package/vendor/fastctx/src/shell/output.rs +406 -0
  119. package/vendor/fastctx/src/shell/process.rs +493 -0
  120. package/vendor/fastctx/src/shell_server.rs +156 -0
  121. package/vendor/fastctx/src/skip_report.rs +83 -0
  122. package/vendor/fastctx/src/stdio_transport.rs +177 -0
  123. package/vendor/fastctx/src/tool_schema.rs +204 -0
  124. package/vendor/fastctx/src/traversal.rs +846 -0
  125. package/vendor/fastctx/third-party/pdfium-7763/LICENSE +9 -0
  126. package/vendor/fastctx/third-party/pdfium-7763/licenses/abseil.txt +202 -0
  127. package/vendor/fastctx/third-party/pdfium-7763/licenses/agg23.txt +14 -0
  128. package/vendor/fastctx/third-party/pdfium-7763/licenses/fast_float.txt +27 -0
  129. package/vendor/fastctx/third-party/pdfium-7763/licenses/freetype.txt +169 -0
  130. package/vendor/fastctx/third-party/pdfium-7763/licenses/icu.txt +542 -0
  131. package/vendor/fastctx/third-party/pdfium-7763/licenses/lcms.txt +27 -0
  132. package/vendor/fastctx/third-party/pdfium-7763/licenses/libjpeg_turbo.ijg +260 -0
  133. package/vendor/fastctx/third-party/pdfium-7763/licenses/libjpeg_turbo.md +135 -0
  134. package/vendor/fastctx/third-party/pdfium-7763/licenses/libopenjpeg.txt +32 -0
  135. package/vendor/fastctx/third-party/pdfium-7763/licenses/libpng.txt +134 -0
  136. package/vendor/fastctx/third-party/pdfium-7763/licenses/libtiff.txt +21 -0
  137. package/vendor/fastctx/third-party/pdfium-7763/licenses/llvm-libc.txt +278 -0
  138. package/vendor/fastctx/third-party/pdfium-7763/licenses/pdfium.txt +230 -0
  139. package/vendor/fastctx/third-party/pdfium-7763/licenses/simdutf.txt +18 -0
  140. package/vendor/fastctx/third-party/pdfium-7763/licenses/zlib.txt +29 -0
@@ -0,0 +1,351 @@
1
+ //! Strict incremental decoding, line collection, and budget closure for text reads.
2
+
3
+ use super::{
4
+ MAX_LINE_CHARS, TOTAL_COUNT_SIZE_LIMIT, UNBOUNDED_LINE_LIMIT, binary_error,
5
+ binary_error_message,
6
+ };
7
+ use crate::budget::{LineTokenCounter, TokenBudget, assemble_text, estimate_tokens};
8
+ use crate::encoding::{EncodingDecision, StreamDecodeFailure, validate_file_encoding};
9
+ use crate::model::ToolResponse;
10
+ use crate::paths::io_error_message;
11
+ use std::fs;
12
+ use std::path::Path;
13
+
14
+ pub(super) fn read_text_file(
15
+ path: &Path,
16
+ path_display: &str,
17
+ offset: Option<usize>,
18
+ limit: Option<usize>,
19
+ explicit_encoding: Option<&str>,
20
+ binary_type: Option<&str>,
21
+ budget: TokenBudget,
22
+ ) -> ToolResponse {
23
+ let offset = offset.unwrap_or(1);
24
+ let limit = limit.unwrap_or(UNBOUNDED_LINE_LIMIT);
25
+ if offset == 0 {
26
+ return ToolResponse::error("Invalid offset value: 0. Expected an integer >= 1.");
27
+ }
28
+ if limit == 0 {
29
+ return ToolResponse::error("Invalid limit value: 0. Expected an integer >= 1.");
30
+ }
31
+ let file_size = match fs::metadata(path) {
32
+ Ok(metadata) => metadata.len(),
33
+ Err(error) => return ToolResponse::error(io_error_message(path, &error)),
34
+ };
35
+ let validated = match validate_file_encoding(path, explicit_encoding) {
36
+ Ok(EncodingDecision::Text(validated)) => validated,
37
+ Ok(EncodingDecision::Binary) => return binary_error(path_display, binary_type),
38
+ Ok(EncodingDecision::Rejected(rejection)) => {
39
+ return ToolResponse::error(rejection.message(path_display));
40
+ }
41
+ Err(error) => return ToolResponse::error(io_error_message(path, &error)),
42
+ };
43
+ if validated.total_lines == 0 {
44
+ return ToolResponse::text("Warning: the file exists but is empty.");
45
+ }
46
+ let transcoding_note = validated.transcoding_note();
47
+
48
+ let total_is_known = file_size <= TOTAL_COUNT_SIZE_LIMIT;
49
+ let mut collector = LineCollector::new(offset, limit, budget.value, total_is_known);
50
+ let exhausted = match validated.stream_text(path, |chunk| collector.push(chunk)) {
51
+ Ok(exhausted) => exhausted,
52
+ Err(StreamDecodeFailure::Io(error)) => {
53
+ return ToolResponse::error(io_error_message(path, &error));
54
+ }
55
+ Err(StreamDecodeFailure::Malformed) => {
56
+ return ToolResponse::error(validated.malformed_rejection().message(path_display));
57
+ }
58
+ };
59
+ if exhausted {
60
+ collector.finish_eof();
61
+ }
62
+ collector.into_response(transcoding_note, budget)
63
+ }
64
+
65
+ pub(super) struct BatchTextContent {
66
+ pub(super) first: usize,
67
+ pub(super) lines: Vec<String>,
68
+ pub(super) total_lines: usize,
69
+ pub(super) total_is_known: bool,
70
+ pub(super) transcoding_note: Option<String>,
71
+ pub(super) slice_complete: bool,
72
+ }
73
+
74
+ #[allow(clippy::too_many_arguments)]
75
+ pub(super) fn read_batch_text_file(
76
+ path: &Path,
77
+ path_display: &str,
78
+ offset: Option<usize>,
79
+ limit: Option<usize>,
80
+ explicit_encoding: Option<&str>,
81
+ binary_type: Option<&str>,
82
+ collection_budget: usize,
83
+ ) -> Result<BatchTextContent, String> {
84
+ let offset = offset.unwrap_or(1);
85
+ let limit = limit.unwrap_or(UNBOUNDED_LINE_LIMIT);
86
+ let file_size = fs::metadata(path)
87
+ .map_err(|error| io_error_message(path, &error))?
88
+ .len();
89
+ let validated = match validate_file_encoding(path, explicit_encoding) {
90
+ Ok(EncodingDecision::Text(validated)) => validated,
91
+ Ok(EncodingDecision::Binary) => {
92
+ return Err(binary_error_message(path_display, binary_type));
93
+ }
94
+ Ok(EncodingDecision::Rejected(rejection)) => {
95
+ return Err(rejection.message(path_display));
96
+ }
97
+ Err(error) => return Err(io_error_message(path, &error)),
98
+ };
99
+ if validated.total_lines == 0 {
100
+ return Err("Warning: the file exists but is empty.".to_string());
101
+ }
102
+ if validated.total_lines < offset {
103
+ let noun = if validated.total_lines == 1 {
104
+ "line"
105
+ } else {
106
+ "lines"
107
+ };
108
+ return Err(format!(
109
+ "Warning: the file has only {} {noun}, but offset={offset} was requested.",
110
+ validated.total_lines
111
+ ));
112
+ }
113
+ let total_lines = validated.total_lines;
114
+ let total_is_known = file_size <= TOTAL_COUNT_SIZE_LIMIT;
115
+ let transcoding_note = validated.transcoding_note();
116
+ let mut collector = LineCollector::new(offset, limit, collection_budget, total_is_known);
117
+ let exhausted = match validated.stream_text(path, |chunk| collector.push(chunk)) {
118
+ Ok(exhausted) => exhausted,
119
+ Err(StreamDecodeFailure::Io(error)) => return Err(io_error_message(path, &error)),
120
+ Err(StreamDecodeFailure::Malformed) => {
121
+ return Err(validated.malformed_rejection().message(path_display));
122
+ }
123
+ };
124
+ if exhausted {
125
+ collector.finish_eof();
126
+ }
127
+ Ok(BatchTextContent {
128
+ first: offset,
129
+ lines: collector.rendered,
130
+ total_lines,
131
+ total_is_known,
132
+ transcoding_note,
133
+ slice_complete: !collector.storage_saturated,
134
+ })
135
+ }
136
+
137
+ struct LineCollector {
138
+ offset: usize,
139
+ request_end: usize,
140
+ budget: usize,
141
+ total_is_known: bool,
142
+ line_number: usize,
143
+ current_prefix: String,
144
+ current_chars: usize,
145
+ current_ends_with_cr: bool,
146
+ decoded_any: bool,
147
+ last_was_newline: bool,
148
+ rendered: Vec<String>,
149
+ body_tokens: LineTokenCounter,
150
+ storage_saturated: bool,
151
+ total_lines: usize,
152
+ stopped_early: bool,
153
+ }
154
+
155
+ impl LineCollector {
156
+ fn new(offset: usize, limit: usize, budget: usize, total_is_known: bool) -> Self {
157
+ Self {
158
+ offset,
159
+ request_end: offset.saturating_add(limit.saturating_sub(1)),
160
+ budget,
161
+ total_is_known,
162
+ line_number: 1,
163
+ current_prefix: String::new(),
164
+ current_chars: 0,
165
+ current_ends_with_cr: false,
166
+ decoded_any: false,
167
+ last_was_newline: false,
168
+ rendered: Vec::new(),
169
+ body_tokens: LineTokenCounter::default(),
170
+ storage_saturated: false,
171
+ total_lines: 0,
172
+ stopped_early: false,
173
+ }
174
+ }
175
+
176
+ fn push(&mut self, text: &str) -> bool {
177
+ for ch in text.chars() {
178
+ self.decoded_any = true;
179
+ if self.line_number > self.request_end && !self.total_is_known {
180
+ self.total_lines = self.line_number;
181
+ self.stopped_early = true;
182
+ return false;
183
+ }
184
+ if ch == '\n' {
185
+ self.last_was_newline = true;
186
+ if !self.finish_line() {
187
+ self.stopped_early = true;
188
+ return false;
189
+ }
190
+ continue;
191
+ }
192
+ self.last_was_newline = false;
193
+ self.current_chars = self.current_chars.saturating_add(1);
194
+ self.current_ends_with_cr = ch == '\r';
195
+ if self.should_capture_current() && self.current_chars <= MAX_LINE_CHARS {
196
+ self.current_prefix.push(ch);
197
+ }
198
+ }
199
+ true
200
+ }
201
+
202
+ fn should_capture_current(&self) -> bool {
203
+ !self.storage_saturated
204
+ && self.line_number >= self.offset
205
+ && self.line_number <= self.request_end
206
+ }
207
+
208
+ fn finish_line(&mut self) -> bool {
209
+ self.total_lines = self.line_number;
210
+ if self.line_number > self.request_end {
211
+ self.reset_line();
212
+ return self.total_is_known;
213
+ }
214
+ if self.line_number >= self.offset && !self.storage_saturated {
215
+ if self.current_ends_with_cr && self.current_chars <= MAX_LINE_CHARS {
216
+ self.current_prefix.pop();
217
+ }
218
+ let total_chars = self
219
+ .current_chars
220
+ .saturating_sub(usize::from(self.current_ends_with_cr));
221
+ let content = if total_chars <= MAX_LINE_CHARS {
222
+ std::mem::take(&mut self.current_prefix)
223
+ } else {
224
+ format!(
225
+ "{}... [line truncated: {total_chars} chars total]",
226
+ self.current_prefix
227
+ )
228
+ };
229
+ let rendered = format!("{}\t{content}", self.line_number);
230
+ let body_tokens = self.body_tokens.push(&rendered);
231
+ self.rendered.push(rendered);
232
+ if body_tokens > self.budget {
233
+ self.storage_saturated = true;
234
+ if !self.total_is_known {
235
+ self.reset_line();
236
+ return false;
237
+ }
238
+ }
239
+ }
240
+ self.reset_line();
241
+ true
242
+ }
243
+
244
+ fn reset_line(&mut self) {
245
+ self.line_number = self.line_number.saturating_add(1);
246
+ self.current_prefix.clear();
247
+ self.current_chars = 0;
248
+ self.current_ends_with_cr = false;
249
+ }
250
+
251
+ fn finish_eof(&mut self) {
252
+ if self.last_was_newline || self.decoded_any || self.current_chars > 0 {
253
+ let _ = self.finish_line();
254
+ }
255
+ }
256
+
257
+ fn into_response(
258
+ mut self,
259
+ transcoding_note: Option<String>,
260
+ budget: TokenBudget,
261
+ ) -> ToolResponse {
262
+ if self.total_lines < self.offset {
263
+ let noun = if self.total_lines == 1 {
264
+ "line"
265
+ } else {
266
+ "lines"
267
+ };
268
+ return ToolResponse::text(format!(
269
+ "Warning: the file has only {} {noun}, but offset={} was requested.",
270
+ self.total_lines, self.offset,
271
+ ));
272
+ }
273
+
274
+ loop {
275
+ let shown = self.rendered.len();
276
+ if shown == 0 && (self.stopped_early || self.storage_saturated) {
277
+ return ToolResponse::error(format!(
278
+ "{}={} is too small to return the required continuation note. Increase it and retry.",
279
+ budget.variable, budget.value
280
+ ));
281
+ }
282
+ let last = self.offset.saturating_add(shown.saturating_sub(1));
283
+ let truncated = self.stopped_early
284
+ || self.storage_saturated
285
+ || (shown > 0 && last < self.total_lines);
286
+ let notes = read_notes(
287
+ transcoding_note.as_deref(),
288
+ truncated,
289
+ self.offset,
290
+ shown,
291
+ self.total_lines,
292
+ self.total_is_known,
293
+ );
294
+ let output = assemble_text(&self.rendered, &notes);
295
+ if estimate_tokens(&output) <= budget.value {
296
+ return ToolResponse::text(output);
297
+ }
298
+ if self.rendered.pop().is_none() {
299
+ return ToolResponse::error(format!(
300
+ "{}={} is too small to return the required continuation note. Increase it and retry.",
301
+ budget.variable, budget.value
302
+ ));
303
+ }
304
+ self.storage_saturated = true;
305
+ }
306
+ }
307
+ }
308
+
309
+ fn read_notes(
310
+ transcoding_note: Option<&str>,
311
+ truncated: bool,
312
+ offset: usize,
313
+ shown: usize,
314
+ total: usize,
315
+ total_is_known: bool,
316
+ ) -> Vec<String> {
317
+ let mut notes = Vec::new();
318
+ if let Some(note) = transcoding_note {
319
+ notes.push(note.to_string());
320
+ }
321
+ if shown > 0 {
322
+ let last = offset + shown - 1;
323
+ let span = line_span(offset, last);
324
+ if truncated {
325
+ if total_is_known {
326
+ notes.push(format!(
327
+ "(Partial: {span} of {total} shown. Continue with offset={}.)",
328
+ last + 1
329
+ ));
330
+ } else {
331
+ notes.push(format!(
332
+ "(Partial: {span} shown. Continue with offset={}.)",
333
+ last + 1
334
+ ));
335
+ }
336
+ } else {
337
+ notes.push(format!(
338
+ "(Complete: reached end of file; {span} of {total} shown.)"
339
+ ));
340
+ }
341
+ }
342
+ notes
343
+ }
344
+
345
+ fn line_span(first: usize, last: usize) -> String {
346
+ if first == last {
347
+ format!("line {first}")
348
+ } else {
349
+ format!("lines {first}-{last}")
350
+ }
351
+ }