Cache shell snapshots in the exec server (#39756)

## What changed

- Add the `shellSnapshotV2` executor capability and an optional shell snapshot request to `ExecParams`.
- Capture and restore Unix shell state and profile exports from an in-memory, attachment-scoped cache for `bash`, `zsh`, and `sh`.
- Apply environment policies, runtime `PATH` entries, sandbox context, and live managed-proxy settings when preparing restored commands.
- Bound snapshot size, capture time, scope length, and cache capacity, and fall back to the original command when capture fails.

## Testing

- Cover local, remote, TTY, sandboxed, and supported-shell execution, plus environment filtering, proxy handling, in-memory reuse, and capture failure fallback.

GitOrigin-RevId: 624f747972c249c88c6f10f42cf0af97b75b5541
This commit is contained in:
jif
2026-08-20 17:13:03 +00:00
committed by copyberry
parent ce950dcf26
commit d0cc662b8c
20 changed files with 799 additions and 1 deletions

View File

@@ -121,6 +121,9 @@ pub struct EnvironmentCapabilities {
/// Whether filesystem streams can use the requested platform sandbox.
#[serde(default)]
pub sandboxed_file_streaming: bool,
/// Whether shell state can be cached and restored entirely inside the executor.
#[serde(default)]
pub shell_snapshot_v2: bool,
}
/// Status returned by an initialized exec-server connection.
@@ -183,6 +186,7 @@ impl EnvironmentInfo {
capability_discovery_sandbox: true,
environment_config_read: true,
sandboxed_file_streaming: true,
shell_snapshot_v2: cfg!(unix),
},
}
}
@@ -219,6 +223,9 @@ pub struct ExecParams {
pub cwd: PathUri,
#[serde(default)]
pub env_policy: Option<ExecEnvPolicy>,
/// Optional request to restore executor-owned, attachment-scoped shell state.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub shell_snapshot: Option<ShellSnapshotRequest>,
pub env: HashMap<String, String>,
pub tty: bool,
/// Keep non-tty stdin writable through `process/write`.
@@ -254,6 +261,19 @@ pub struct ExecEnvPolicy {
pub include_only: Vec<String>,
}
/// Identifies shell state owned by one attachment within an executor session.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ShellSnapshotRequest {
/// Attachment identity; executor sessions independently scope every cache.
pub scope_id: String,
/// Executor-native shell used to capture and restore the snapshot.
pub shell: ShellInfo,
/// Runtime-owned PATH entries to replay after restoring profile state.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub runtime_path_prepends: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExecResponse {
@@ -849,6 +869,7 @@ mod tests {
argv: vec!["true".to_string()],
cwd,
env_policy: None,
shell_snapshot: None,
env: HashMap::new(),
tty: false,
pipe_stdin: false,
@@ -943,6 +964,7 @@ mod tests {
capability_discovery_sandbox: true,
environment_config_read: false,
sandboxed_file_streaming: false,
shell_snapshot_v2: false,
}
);
}
@@ -958,6 +980,7 @@ mod tests {
"capabilityDiscoverySandbox": false,
"environmentConfigRead": false,
"sandboxedFileStreaming": false,
"shellSnapshotV2": false,
},
});
let info: EnvironmentInfo = serde_json::from_value(expected.clone())