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.
- package/CHANGELOG.md +202 -0
- package/LICENSE +30 -0
- package/NOTICE +106 -0
- package/PROVENANCE.md +435 -0
- package/README.en.md +126 -0
- package/README.md +115 -2
- package/README.zh.md +116 -0
- package/bin/dsh-ops.mjs +1216 -0
- package/cordis.patch.yml +160 -0
- package/docs/manual-validation.md +53 -0
- package/docs/release-0.2.1.md +72 -0
- package/docs/schema-baseline.json +64 -0
- package/docs/schema-current.json +84 -0
- package/docs/schema-measurement.md +17 -0
- package/dsh-plugin.json +88 -0
- package/icon.svg +12 -0
- package/lib/binary.js +409 -0
- package/lib/config.js +198 -0
- package/lib/handshake.js +252 -0
- package/lib/index.js +108 -0
- package/lib/jobs.js +42 -0
- package/lib/policy.js +64 -0
- package/lib/presentation.js +63 -0
- package/lib/profile-install.js +61 -0
- package/lib/rust.js +194 -0
- package/lib/session-shells.js +78 -0
- package/lib/shells.js +998 -0
- package/lib/tools.js +657 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +114 -4
- package/vendor/fastctx/Cargo.lock +3210 -0
- package/vendor/fastctx/Cargo.toml +94 -0
- package/vendor/fastctx/FORK.md +119 -0
- package/vendor/fastctx/LICENSE-APACHE +201 -0
- package/vendor/fastctx/NOTICE +40 -0
- package/vendor/fastctx/README.md +439 -0
- package/vendor/fastctx/THIRD_PARTY_LICENSES.md +17 -0
- package/vendor/fastctx/THIRD_PARTY_LICENSES_RUST.md +7914 -0
- package/vendor/fastctx/UPSTREAM.md +49 -0
- package/vendor/fastctx/build.rs +413 -0
- package/vendor/fastctx/src/background_status.rs +403 -0
- package/vendor/fastctx/src/binary.rs +75 -0
- package/vendor/fastctx/src/bounded_sort.rs +500 -0
- package/vendor/fastctx/src/budget.rs +781 -0
- package/vendor/fastctx/src/cli/mod.rs +110 -0
- package/vendor/fastctx/src/context_guard.rs +289 -0
- package/vendor/fastctx/src/control/mod.rs +6 -0
- package/vendor/fastctx/src/control/paths.rs +49 -0
- package/vendor/fastctx/src/control/settings.rs +753 -0
- package/vendor/fastctx/src/control/transaction.rs +531 -0
- package/vendor/fastctx/src/edit/document.rs +535 -0
- package/vendor/fastctx/src/edit/locks.rs +371 -0
- package/vendor/fastctx/src/edit/mod.rs +213 -0
- package/vendor/fastctx/src/edit/private_storage/unix.rs +315 -0
- package/vendor/fastctx/src/edit/private_storage/windows.rs +793 -0
- package/vendor/fastctx/src/edit/private_storage.rs +234 -0
- package/vendor/fastctx/src/edit/replace.rs +1030 -0
- package/vendor/fastctx/src/edit_server.rs +53 -0
- package/vendor/fastctx/src/encoding/reference_v011.rs +587 -0
- package/vendor/fastctx/src/encoding/snapshot_pipeline.rs +1678 -0
- package/vendor/fastctx/src/encoding.rs +1118 -0
- package/vendor/fastctx/src/file_executor.rs +1151 -0
- package/vendor/fastctx/src/file_snapshot.rs +1491 -0
- package/vendor/fastctx/src/glob_filter.rs +98 -0
- package/vendor/fastctx/src/glob_tool.rs +653 -0
- package/vendor/fastctx/src/grep_sink.rs +1162 -0
- package/vendor/fastctx/src/grep_tool.rs +2449 -0
- package/vendor/fastctx/src/lib.rs +45 -0
- package/vendor/fastctx/src/main.rs +15 -0
- package/vendor/fastctx/src/model.rs +51 -0
- package/vendor/fastctx/src/model_guidance.rs +62 -0
- package/vendor/fastctx/src/operation.rs +356 -0
- package/vendor/fastctx/src/ordered_window.rs +1235 -0
- package/vendor/fastctx/src/os_environment.rs +414 -0
- package/vendor/fastctx/src/path_codec.rs +850 -0
- package/vendor/fastctx/src/paths.rs +244 -0
- package/vendor/fastctx/src/process_identity.rs +763 -0
- package/vendor/fastctx/src/process_policy.rs +74 -0
- package/vendor/fastctx/src/read_tool/batch.rs +496 -0
- package/vendor/fastctx/src/read_tool/hex_file.rs +141 -0
- package/vendor/fastctx/src/read_tool/image_file.rs +88 -0
- package/vendor/fastctx/src/read_tool/mod.rs +245 -0
- package/vendor/fastctx/src/read_tool/pdf.rs +470 -0
- package/vendor/fastctx/src/read_tool/pdf_disabled.rs +47 -0
- package/vendor/fastctx/src/read_tool/pdf_engine.rs +664 -0
- package/vendor/fastctx/src/read_tool/text_file.rs +351 -0
- package/vendor/fastctx/src/render_plan.rs +468 -0
- package/vendor/fastctx/src/runtime/activity.rs +159 -0
- package/vendor/fastctx/src/runtime/hosts.rs +99 -0
- package/vendor/fastctx/src/runtime/journal.rs +556 -0
- package/vendor/fastctx/src/runtime/local_ipc.rs +186 -0
- package/vendor/fastctx/src/runtime/mod.rs +746 -0
- package/vendor/fastctx/src/runtime/protocol.rs +296 -0
- package/vendor/fastctx/src/runtime/session.rs +536 -0
- package/vendor/fastctx/src/runtime/windows_process.rs +66 -0
- package/vendor/fastctx/src/search_parallelism.rs +106 -0
- package/vendor/fastctx/src/search_text.rs +227 -0
- package/vendor/fastctx/src/server.rs +359 -0
- package/vendor/fastctx/src/server_manifest.rs +468 -0
- package/vendor/fastctx/src/server_support.rs +826 -0
- package/vendor/fastctx/src/session.rs +629 -0
- package/vendor/fastctx/src/shell/apply_patch_hint.rs +41 -0
- package/vendor/fastctx/src/shell/bash.rs +263 -0
- package/vendor/fastctx/src/shell/buffer.rs +108 -0
- package/vendor/fastctx/src/shell/encoding.rs +403 -0
- package/vendor/fastctx/src/shell/foreground.rs +115 -0
- package/vendor/fastctx/src/shell/jobs/admission.rs +91 -0
- package/vendor/fastctx/src/shell/jobs/background.rs +146 -0
- package/vendor/fastctx/src/shell/jobs/host.rs +830 -0
- package/vendor/fastctx/src/shell/jobs/identity.rs +29 -0
- package/vendor/fastctx/src/shell/jobs/mod.rs +1513 -0
- package/vendor/fastctx/src/shell/jobs/model.rs +244 -0
- package/vendor/fastctx/src/shell/jobs/output_log.rs +1148 -0
- package/vendor/fastctx/src/shell/jobs/store.rs +1300 -0
- package/vendor/fastctx/src/shell/mod.rs +345 -0
- package/vendor/fastctx/src/shell/normalize.rs +389 -0
- package/vendor/fastctx/src/shell/output.rs +406 -0
- package/vendor/fastctx/src/shell/process.rs +493 -0
- package/vendor/fastctx/src/shell_server.rs +156 -0
- package/vendor/fastctx/src/skip_report.rs +83 -0
- package/vendor/fastctx/src/stdio_transport.rs +177 -0
- package/vendor/fastctx/src/tool_schema.rs +204 -0
- package/vendor/fastctx/src/traversal.rs +846 -0
- package/vendor/fastctx/third-party/pdfium-7763/LICENSE +9 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/abseil.txt +202 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/agg23.txt +14 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/fast_float.txt +27 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/freetype.txt +169 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/icu.txt +542 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/lcms.txt +27 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libjpeg_turbo.ijg +260 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libjpeg_turbo.md +135 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libopenjpeg.txt +32 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libpng.txt +134 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libtiff.txt +21 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/llvm-libc.txt +278 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/pdfium.txt +230 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/simdutf.txt +18 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/zlib.txt +29 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Modified by dsh-ops (https://github.com/T-Auto/dsh-ops): removed the TUI, Apply/Unapply/Status/Lang/Jobs/UpdateHelper commands and the updater startup hooks, leaving the MCP server as the default command.
|
|
2
|
+
//! Command-line parsing for the MCP server and its internal child-process entry points.
|
|
3
|
+
|
|
4
|
+
use crate::server::ServerOptions;
|
|
5
|
+
use clap::{Parser, Subcommand};
|
|
6
|
+
use std::process::ExitCode;
|
|
7
|
+
|
|
8
|
+
/// fastctx MCP server entry point.
|
|
9
|
+
#[derive(Debug, Parser)]
|
|
10
|
+
#[command(
|
|
11
|
+
name = "fastctx",
|
|
12
|
+
version,
|
|
13
|
+
about = "FastCtx — fast, context-efficient repository tools for AI agents.",
|
|
14
|
+
long_about = "Runs the FastCtx MCP server on stdin/stdout."
|
|
15
|
+
)]
|
|
16
|
+
pub struct Cli {
|
|
17
|
+
/// Explicit command; omission starts the MCP server.
|
|
18
|
+
#[command(subcommand)]
|
|
19
|
+
command: Option<Command>,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// The server command plus the internal child-process entry points.
|
|
23
|
+
#[derive(Debug, Subcommand)]
|
|
24
|
+
enum Command {
|
|
25
|
+
/// Force the stdio MCP server.
|
|
26
|
+
Serve {
|
|
27
|
+
/// Publish the five optional shell tools.
|
|
28
|
+
#[arg(long)]
|
|
29
|
+
enable_shell: bool,
|
|
30
|
+
/// Deprecated compatibility flag; replace is always published.
|
|
31
|
+
#[arg(long, hide = true)]
|
|
32
|
+
enable_edit: bool,
|
|
33
|
+
},
|
|
34
|
+
/// Internal Unix detach bootstrap.
|
|
35
|
+
#[cfg(unix)]
|
|
36
|
+
#[command(hide = true)]
|
|
37
|
+
JobBootstrap,
|
|
38
|
+
/// Internal detached background-job supervisor.
|
|
39
|
+
#[command(hide = true)]
|
|
40
|
+
JobHost,
|
|
41
|
+
/// Internal Unix process-group orphan guard.
|
|
42
|
+
#[cfg(unix)]
|
|
43
|
+
#[command(hide = true)]
|
|
44
|
+
JobWatch { pid: u32, started: String },
|
|
45
|
+
/// Internal short-lived control-center detach bootstrap.
|
|
46
|
+
#[command(hide = true)]
|
|
47
|
+
RuntimeBootstrap,
|
|
48
|
+
/// Internal per-user control center.
|
|
49
|
+
#[command(hide = true)]
|
|
50
|
+
RuntimeHost {
|
|
51
|
+
/// Test-only idle override; production bootstraps always use ten minutes.
|
|
52
|
+
#[arg(long, hide = true)]
|
|
53
|
+
idle_timeout_ms: Option<u64>,
|
|
54
|
+
/// Test-only maintenance override; production bootstraps use one minute.
|
|
55
|
+
#[arg(long, hide = true)]
|
|
56
|
+
maintenance_interval_ms: Option<u64>,
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Parses the current process arguments and executes the selected command.
|
|
61
|
+
pub async fn run() -> Result<ExitCode, String> {
|
|
62
|
+
run_cli(Cli::parse()).await
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async fn run_cli(cli: Cli) -> Result<ExitCode, String> {
|
|
66
|
+
match cli.command {
|
|
67
|
+
Some(Command::Serve {
|
|
68
|
+
enable_shell,
|
|
69
|
+
enable_edit: _,
|
|
70
|
+
}) => run_server_with_options(ServerOptions { enable_shell }).await,
|
|
71
|
+
#[cfg(unix)]
|
|
72
|
+
Some(Command::JobBootstrap) => {
|
|
73
|
+
crate::shell::jobs::run_bootstrap_entry()?;
|
|
74
|
+
Ok(ExitCode::SUCCESS)
|
|
75
|
+
}
|
|
76
|
+
Some(Command::JobHost) => {
|
|
77
|
+
crate::shell::jobs::run_host_entry()?;
|
|
78
|
+
Ok(ExitCode::SUCCESS)
|
|
79
|
+
}
|
|
80
|
+
Some(Command::RuntimeBootstrap) => {
|
|
81
|
+
crate::runtime::run_bootstrap_entry()?;
|
|
82
|
+
Ok(ExitCode::SUCCESS)
|
|
83
|
+
}
|
|
84
|
+
Some(Command::RuntimeHost {
|
|
85
|
+
idle_timeout_ms,
|
|
86
|
+
maintenance_interval_ms,
|
|
87
|
+
}) => {
|
|
88
|
+
crate::runtime::run_host_entry(idle_timeout_ms, maintenance_interval_ms).await?;
|
|
89
|
+
Ok(ExitCode::SUCCESS)
|
|
90
|
+
}
|
|
91
|
+
#[cfg(unix)]
|
|
92
|
+
Some(Command::JobWatch { pid, started }) => {
|
|
93
|
+
crate::shell::jobs::run_watchdog_entry(pid, started)?;
|
|
94
|
+
Ok(ExitCode::SUCCESS)
|
|
95
|
+
}
|
|
96
|
+
None => run_server().await,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// Forces stdio MCP server startup for reuse by the entry point and doctor.
|
|
101
|
+
pub async fn run_server() -> Result<ExitCode, String> {
|
|
102
|
+
run_server_with_options(ServerOptions::default()).await
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// Starts the single server with the requested optional tool groups.
|
|
106
|
+
pub async fn run_server_with_options(options: ServerOptions) -> Result<ExitCode, String> {
|
|
107
|
+
let environment = crate::runtime::capture_proxy_environment()?;
|
|
108
|
+
let parent = crate::process_identity::parent_identity_from_environment()?;
|
|
109
|
+
crate::runtime::run_proxy_session(options, environment, parent).await
|
|
110
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
//! Per-session aggregate output admission for Guarded provider routes.
|
|
2
|
+
|
|
3
|
+
use std::sync::{Arc, Mutex};
|
|
4
|
+
use std::time::{Duration, Instant};
|
|
5
|
+
|
|
6
|
+
/// Calls completing within this interval remain in the same model-turn burst.
|
|
7
|
+
pub(crate) const BURST_GAP: Duration = Duration::from_secs(1);
|
|
8
|
+
/// Minimum formatter allowance after the shared pool can no longer fund a normal response.
|
|
9
|
+
pub(crate) const STUB_TOKEN_BUDGET: usize = 128;
|
|
10
|
+
/// Codex 0.147.0's inner auto-to-effective buffer for a 272K model catalog entry.
|
|
11
|
+
pub(crate) const INNER_COMPACTION_BUFFER: usize = 13_600;
|
|
12
|
+
/// Absolute text-accounting ceiling; one token remains between FastCtx and Codex's buffer.
|
|
13
|
+
pub(crate) const BURST_HARD_TOKEN_LIMIT: usize = INNER_COMPACTION_BUFFER - 1;
|
|
14
|
+
|
|
15
|
+
/// Shared output state owned by one MCP connection, never by the per-user runtime.
|
|
16
|
+
#[derive(Debug)]
|
|
17
|
+
pub(crate) struct GuardedBurstPool {
|
|
18
|
+
token_budget: usize,
|
|
19
|
+
gap: Duration,
|
|
20
|
+
state: Mutex<BurstState>,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#[derive(Debug)]
|
|
24
|
+
struct BurstState {
|
|
25
|
+
generation: u64,
|
|
26
|
+
active_calls: usize,
|
|
27
|
+
unclaimed_calls: usize,
|
|
28
|
+
spent_tokens: usize,
|
|
29
|
+
reserved_tokens: usize,
|
|
30
|
+
last_completed: Option<Instant>,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
impl GuardedBurstPool {
|
|
34
|
+
pub(crate) fn new(token_budget: usize, gap: Duration) -> Arc<Self> {
|
|
35
|
+
Arc::new(Self {
|
|
36
|
+
token_budget,
|
|
37
|
+
gap,
|
|
38
|
+
state: Mutex::new(BurstState {
|
|
39
|
+
generation: 0,
|
|
40
|
+
active_calls: 0,
|
|
41
|
+
unclaimed_calls: 0,
|
|
42
|
+
spent_tokens: 0,
|
|
43
|
+
reserved_tokens: 0,
|
|
44
|
+
last_completed: None,
|
|
45
|
+
}),
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
pub(crate) fn begin(self: &Arc<Self>) -> BurstTicket {
|
|
50
|
+
self.begin_at(Instant::now())
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fn begin_at(self: &Arc<Self>, now: Instant) -> BurstTicket {
|
|
54
|
+
let mut state = self.state.lock().expect("Guarded burst state was poisoned");
|
|
55
|
+
let starts_new = state.active_calls == 0
|
|
56
|
+
&& state
|
|
57
|
+
.last_completed
|
|
58
|
+
.is_none_or(|completed| now.saturating_duration_since(completed) >= self.gap);
|
|
59
|
+
if starts_new {
|
|
60
|
+
state.generation = state.generation.wrapping_add(1);
|
|
61
|
+
state.spent_tokens = 0;
|
|
62
|
+
state.reserved_tokens = 0;
|
|
63
|
+
}
|
|
64
|
+
state.active_calls = state.active_calls.saturating_add(1);
|
|
65
|
+
state.unclaimed_calls = state.unclaimed_calls.saturating_add(1);
|
|
66
|
+
BurstTicket {
|
|
67
|
+
pool: Arc::clone(self),
|
|
68
|
+
generation: state.generation,
|
|
69
|
+
active: true,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#[cfg(test)]
|
|
74
|
+
fn spent_tokens(&self) -> usize {
|
|
75
|
+
self.state
|
|
76
|
+
.lock()
|
|
77
|
+
.expect("Guarded burst state was poisoned")
|
|
78
|
+
.spent_tokens
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// Arrival record that keeps queued sibling calls visible to later render-time allocation.
|
|
83
|
+
#[must_use]
|
|
84
|
+
pub(crate) struct BurstTicket {
|
|
85
|
+
pool: Arc<GuardedBurstPool>,
|
|
86
|
+
generation: u64,
|
|
87
|
+
active: bool,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
impl BurstTicket {
|
|
91
|
+
/// Reserves a fair render allowance among calls that have not yet claimed one.
|
|
92
|
+
pub(crate) fn claim(mut self, normal_budget: usize) -> BurstClaim {
|
|
93
|
+
let (allowance, exhausted) = {
|
|
94
|
+
let mut state = self
|
|
95
|
+
.pool
|
|
96
|
+
.state
|
|
97
|
+
.lock()
|
|
98
|
+
.expect("Guarded burst state was poisoned");
|
|
99
|
+
debug_assert_eq!(state.generation, self.generation);
|
|
100
|
+
debug_assert!(state.unclaimed_calls > 0);
|
|
101
|
+
let remaining = self
|
|
102
|
+
.pool
|
|
103
|
+
.token_budget
|
|
104
|
+
.saturating_sub(state.spent_tokens.saturating_add(state.reserved_tokens));
|
|
105
|
+
let hard_remaining = BURST_HARD_TOKEN_LIMIT
|
|
106
|
+
.saturating_sub(state.spent_tokens.saturating_add(state.reserved_tokens));
|
|
107
|
+
let fair_share = remaining / state.unclaimed_calls;
|
|
108
|
+
let allowance = normal_budget
|
|
109
|
+
.min(fair_share.max(STUB_TOKEN_BUDGET))
|
|
110
|
+
.min(hard_remaining);
|
|
111
|
+
state.unclaimed_calls -= 1;
|
|
112
|
+
state.reserved_tokens = state.reserved_tokens.saturating_add(allowance);
|
|
113
|
+
(
|
|
114
|
+
allowance,
|
|
115
|
+
remaining < STUB_TOKEN_BUDGET || hard_remaining < STUB_TOKEN_BUDGET,
|
|
116
|
+
)
|
|
117
|
+
};
|
|
118
|
+
self.active = false;
|
|
119
|
+
BurstClaim {
|
|
120
|
+
pool: Arc::clone(&self.pool),
|
|
121
|
+
generation: self.generation,
|
|
122
|
+
allowance,
|
|
123
|
+
exhausted,
|
|
124
|
+
active: true,
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
impl Drop for BurstTicket {
|
|
130
|
+
fn drop(&mut self) {
|
|
131
|
+
if !self.active {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
let mut state = self
|
|
135
|
+
.pool
|
|
136
|
+
.state
|
|
137
|
+
.lock()
|
|
138
|
+
.expect("Guarded burst state was poisoned");
|
|
139
|
+
debug_assert_eq!(state.generation, self.generation);
|
|
140
|
+
state.active_calls = state.active_calls.saturating_sub(1);
|
|
141
|
+
state.unclaimed_calls = state.unclaimed_calls.saturating_sub(1);
|
|
142
|
+
state.last_completed = Some(Instant::now());
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/// Reserved formatter share; dropping it refunds the reservation after panic or cancellation.
|
|
147
|
+
#[must_use]
|
|
148
|
+
pub(crate) struct BurstClaim {
|
|
149
|
+
pool: Arc<GuardedBurstPool>,
|
|
150
|
+
generation: u64,
|
|
151
|
+
allowance: usize,
|
|
152
|
+
exhausted: bool,
|
|
153
|
+
active: bool,
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
impl BurstClaim {
|
|
157
|
+
pub(crate) const fn allowance(&self) -> usize {
|
|
158
|
+
self.allowance
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
pub(crate) const fn exhausted(&self) -> bool {
|
|
162
|
+
self.exhausted
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
pub(crate) fn complete(mut self, actual_tokens: usize) {
|
|
166
|
+
self.complete_at(actual_tokens, Instant::now());
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
fn complete_at(&mut self, actual_tokens: usize, now: Instant) {
|
|
170
|
+
if !self.active {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
let mut state = self
|
|
174
|
+
.pool
|
|
175
|
+
.state
|
|
176
|
+
.lock()
|
|
177
|
+
.expect("Guarded burst state was poisoned");
|
|
178
|
+
debug_assert_eq!(state.generation, self.generation);
|
|
179
|
+
state.reserved_tokens = state.reserved_tokens.saturating_sub(self.allowance);
|
|
180
|
+
state.spent_tokens = state.spent_tokens.saturating_add(actual_tokens);
|
|
181
|
+
state.active_calls = state.active_calls.saturating_sub(1);
|
|
182
|
+
state.last_completed = Some(now);
|
|
183
|
+
self.active = false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
impl Drop for BurstClaim {
|
|
188
|
+
fn drop(&mut self) {
|
|
189
|
+
if !self.active {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
let mut state = self
|
|
193
|
+
.pool
|
|
194
|
+
.state
|
|
195
|
+
.lock()
|
|
196
|
+
.expect("Guarded burst state was poisoned");
|
|
197
|
+
debug_assert_eq!(state.generation, self.generation);
|
|
198
|
+
state.reserved_tokens = state.reserved_tokens.saturating_sub(self.allowance);
|
|
199
|
+
state.active_calls = state.active_calls.saturating_sub(1);
|
|
200
|
+
state.last_completed = Some(Instant::now());
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
#[cfg(test)]
|
|
205
|
+
mod tests {
|
|
206
|
+
use super::{
|
|
207
|
+
BURST_GAP, BURST_HARD_TOKEN_LIMIT, GuardedBurstPool, INNER_COMPACTION_BUFFER,
|
|
208
|
+
STUB_TOKEN_BUDGET,
|
|
209
|
+
};
|
|
210
|
+
use std::time::{Duration, Instant};
|
|
211
|
+
|
|
212
|
+
const POOL: usize = 9_000;
|
|
213
|
+
|
|
214
|
+
#[test]
|
|
215
|
+
fn three_overlapping_calls_receive_equal_render_shares() {
|
|
216
|
+
let now = Instant::now();
|
|
217
|
+
let pool = GuardedBurstPool::new(POOL, BURST_GAP);
|
|
218
|
+
let tickets = [pool.begin_at(now), pool.begin_at(now), pool.begin_at(now)];
|
|
219
|
+
let claims = tickets.map(|ticket| ticket.claim(POOL));
|
|
220
|
+
assert_eq!(claims.each_ref().map(|claim| claim.allowance()), [3_000; 3]);
|
|
221
|
+
for mut claim in claims {
|
|
222
|
+
let allowance = claim.allowance();
|
|
223
|
+
claim.complete_at(allowance, now);
|
|
224
|
+
}
|
|
225
|
+
assert_eq!(pool.spent_tokens(), POOL);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
#[test]
|
|
229
|
+
fn same_turn_serial_calls_keep_the_spent_pool_and_next_turn_resets_it() {
|
|
230
|
+
let now = Instant::now();
|
|
231
|
+
let pool = GuardedBurstPool::new(POOL, BURST_GAP);
|
|
232
|
+
let mut first = pool.begin_at(now).claim(POOL);
|
|
233
|
+
assert_eq!(first.allowance(), POOL);
|
|
234
|
+
first.complete_at(POOL, now);
|
|
235
|
+
|
|
236
|
+
let mut same_turn = pool.begin_at(now + Duration::from_millis(100)).claim(POOL);
|
|
237
|
+
assert_eq!(same_turn.allowance(), STUB_TOKEN_BUDGET);
|
|
238
|
+
assert!(same_turn.exhausted());
|
|
239
|
+
same_turn.complete_at(0, now + Duration::from_millis(100));
|
|
240
|
+
|
|
241
|
+
let next_turn = pool
|
|
242
|
+
.begin_at(now + BURST_GAP + Duration::from_millis(100))
|
|
243
|
+
.claim(POOL);
|
|
244
|
+
assert_eq!(next_turn.allowance(), POOL);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
#[test]
|
|
248
|
+
fn session_pools_never_share_spend() {
|
|
249
|
+
let now = Instant::now();
|
|
250
|
+
let first = GuardedBurstPool::new(POOL, BURST_GAP);
|
|
251
|
+
let second = GuardedBurstPool::new(POOL, BURST_GAP);
|
|
252
|
+
let mut spent = first.begin_at(now).claim(POOL);
|
|
253
|
+
spent.complete_at(POOL, now);
|
|
254
|
+
assert_eq!(
|
|
255
|
+
second
|
|
256
|
+
.begin_at(now + Duration::from_millis(10))
|
|
257
|
+
.claim(POOL)
|
|
258
|
+
.allowance(),
|
|
259
|
+
POOL
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
#[test]
|
|
264
|
+
fn every_simultaneous_tool_lane_stays_inside_the_inner_compaction_buffer() {
|
|
265
|
+
// The runtime admits at most 8 file + 16 shell + 8 replace formatters at once.
|
|
266
|
+
const MAX_SIMULTANEOUS_FORMATTERS: usize = 32;
|
|
267
|
+
const {
|
|
268
|
+
assert!(
|
|
269
|
+
POOL + (MAX_SIMULTANEOUS_FORMATTERS - 1) * STUB_TOKEN_BUDGET
|
|
270
|
+
< INNER_COMPACTION_BUFFER
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
#[test]
|
|
276
|
+
fn an_unbounded_same_burst_sequence_can_never_cross_the_hard_buffer() {
|
|
277
|
+
let now = Instant::now();
|
|
278
|
+
let pool = GuardedBurstPool::new(POOL, BURST_GAP);
|
|
279
|
+
for index in 0..1_000_u64 {
|
|
280
|
+
let mut claim = pool
|
|
281
|
+
.begin_at(now + Duration::from_micros(index))
|
|
282
|
+
.claim(POOL);
|
|
283
|
+
let worst_case = claim.allowance();
|
|
284
|
+
claim.complete_at(worst_case, now + Duration::from_micros(index));
|
|
285
|
+
}
|
|
286
|
+
assert_eq!(pool.spent_tokens(), BURST_HARD_TOKEN_LIMIT);
|
|
287
|
+
assert!(pool.spent_tokens() < INNER_COMPACTION_BUFFER);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Modified by dsh-ops (https://github.com/T-Auto/dsh-ops): kept only the paths/settings/transaction modules the MCP server loads, dropping the Codex integration, doctor, and language catalogs.
|
|
2
|
+
//! Per-user paths, settings, and atomic file transactions used by the MCP server.
|
|
3
|
+
|
|
4
|
+
pub mod paths;
|
|
5
|
+
pub mod settings;
|
|
6
|
+
pub mod transaction;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Modified by dsh-ops (https://github.com/T-Auto/dsh-ops): removed the Codex profile, installed-binary, and discovery paths that only the deleted Codex integration and self-updater used.
|
|
2
|
+
//! Stable user-directory paths used by the MCP server.
|
|
3
|
+
|
|
4
|
+
use std::path::PathBuf;
|
|
5
|
+
|
|
6
|
+
/// Per-user state directories the server resolves once per connection.
|
|
7
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
8
|
+
pub struct ControlPaths {
|
|
9
|
+
/// User home directory.
|
|
10
|
+
pub home: PathBuf,
|
|
11
|
+
/// FastCtx configuration directory.
|
|
12
|
+
pub fastctx_dir: PathBuf,
|
|
13
|
+
/// FastCtx configuration file.
|
|
14
|
+
pub fastctx_config: PathBuf,
|
|
15
|
+
/// Persistent background-job registry and complete output-log directory.
|
|
16
|
+
pub jobs_dir: PathBuf,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
impl ControlPaths {
|
|
20
|
+
/// Builds paths for a supplied home directory for isolated installs and contract tests.
|
|
21
|
+
pub fn for_home(home: impl Into<PathBuf>) -> Self {
|
|
22
|
+
let home = home.into();
|
|
23
|
+
let fastctx_dir = home.join(".fastctx");
|
|
24
|
+
Self {
|
|
25
|
+
fastctx_config: fastctx_dir.join("config.toml"),
|
|
26
|
+
jobs_dir: fastctx_dir.join("jobs"),
|
|
27
|
+
home,
|
|
28
|
+
fastctx_dir,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#[cfg(test)]
|
|
34
|
+
mod tests {
|
|
35
|
+
use super::ControlPaths;
|
|
36
|
+
|
|
37
|
+
#[test]
|
|
38
|
+
fn fastctx_state_lives_under_the_supplied_home() {
|
|
39
|
+
let home = std::path::PathBuf::from("example-home");
|
|
40
|
+
let paths = ControlPaths::for_home(&home);
|
|
41
|
+
|
|
42
|
+
assert_eq!(paths.fastctx_dir, home.join(".fastctx"));
|
|
43
|
+
assert_eq!(
|
|
44
|
+
paths.fastctx_config,
|
|
45
|
+
home.join(".fastctx").join("config.toml")
|
|
46
|
+
);
|
|
47
|
+
assert_eq!(paths.jobs_dir, home.join(".fastctx").join("jobs"));
|
|
48
|
+
}
|
|
49
|
+
}
|