pub(crate) mod bundle;
pub(crate) mod detect;
pub(crate) mod identity;
pub(crate) mod receipt;
pub(crate) mod skin;
#[cfg(test)]
mod tests;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
pub(crate) use bundle::{BundleAvailability, DshAppBundle, DshBundleRecord};
pub(crate) use detect::{DetectEnv, DshCompatibility, DshDetection, DshRunner, ProcessRunner};
pub(crate) use identity::{
CodewhaleRouteIdentity, DshAdapter, MappedIdentity, WireProtocol, map_identity, render_overlay,
sha256_hex,
};
pub(crate) use receipt::{
DshConnectionRecord, DshReceiptDocument, DshReceiptEntry, DshReceiptEvent, now_rfc3339,
write_atomic,
};
pub(crate) const INTEGRATION_DIR: &str = "integrations/dsh";
pub(crate) const OVERLAY_FILE: &str = "codewhale.patch.yml";
pub(crate) const RECEIPT_FILE: &str = "receipt.json";
pub(crate) const SKIN_FILE: &str = "codewhale-dsh-skin.css";
pub(crate) const SKIN_PREVIEW_FILE: &str = "codewhale-dsh-skin-preview.html";
pub(crate) const RELATIONSHIP_LABEL: &str = "DeepSeek Harness connected through Codewhale";
pub(crate) const CLI_COMMAND: &str = "codewhale integrations dsh";
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct DshPaths {
pub(crate) root: PathBuf,
pub(crate) overlay: PathBuf,
pub(crate) receipt: PathBuf,
pub(crate) skin: PathBuf,
pub(crate) skin_preview: PathBuf,
pub(crate) bundle_dir: PathBuf,
}
impl DshPaths {
pub(crate) fn under(codewhale_home: &Path) -> Self {
let root = codewhale_home.join(INTEGRATION_DIR);
Self {
overlay: root.join(OVERLAY_FILE),
receipt: root.join(RECEIPT_FILE),
skin: root.join(SKIN_FILE),
skin_preview: root.join(SKIN_PREVIEW_FILE),
bundle_dir: root.join(bundle::BUNDLE_DIR),
root,
}
}
pub(crate) fn from_process() -> Result<Self> {
Ok(Self::under(&codewhale_config::codewhale_home()?))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "state", rename_all = "kebab-case")]
pub(crate) enum DshIntegrationState {
NotInstalled,
Offline { reason: String },
Incompatible {
version: Option<String>,
reason: String,
},
Detected { version: String },
Connected { version: String },
StaleConfig { version: String, reason: String },
StaleVersion { version: String, verified: String },
Disabled { version: Option<String> },
}
impl DshIntegrationState {
pub(crate) fn label(&self) -> &'static str {
match self {
Self::NotInstalled => "not-installed",
Self::Offline { .. } => "offline",
Self::Incompatible { .. } => "incompatible",
Self::Detected { .. } => "detected",
Self::Connected { .. } => "connected",
Self::StaleConfig { .. } => "stale-config",
Self::StaleVersion { .. } => "stale-version",
Self::Disabled { .. } => "disabled",
}
}
pub(crate) fn launchable(&self) -> bool {
matches!(self, Self::Connected { .. } | Self::StaleVersion { .. })
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct DshStatusReport {
pub(crate) state: DshIntegrationState,
pub(crate) detection: DshDetection,
pub(crate) record: Option<DshConnectionRecord>,
pub(crate) overlay_present: bool,
pub(crate) overlay_sha256_on_disk: Option<String>,
pub(crate) current_identity: Option<MappedIdentity>,
pub(crate) current_identity_error: Option<String>,
pub(crate) shadowing_namespaces: Vec<String>,
pub(crate) bundle_availability: BundleAvailability,
pub(crate) bundle_profile_bundles: Option<Vec<String>>,
pub(crate) bundle_patch_present: bool,
pub(crate) paths_root: PathBuf,
pub(crate) overlay_path: PathBuf,
pub(crate) receipt_path: PathBuf,
}
const SHADOWING_NAMESPACES: &[&str] = &["agent-default-model", "llm-deepseek", "llm-pi-ai"];
pub(crate) fn shadowing_namespaces(detection: &DshDetection) -> Vec<String> {
detection
.settings_namespaces
.iter()
.filter(|ns| SHADOWING_NAMESPACES.contains(&ns.as_str()))
.cloned()
.collect()
}
pub(crate) fn compute_status(
paths: &DshPaths,
detection: DshDetection,
current_identity: Result<CodewhaleRouteIdentity, String>,
allow_full_access: bool,
bundle_availability: BundleAvailability,
) -> Result<DshStatusReport> {
let doc = DshReceiptDocument::load(&paths.receipt)?;
let record = doc.current;
let overlay_bytes = std::fs::read(&paths.overlay).ok();
let overlay_present = overlay_bytes.is_some();
let overlay_sha256_on_disk = overlay_bytes.as_deref().map(sha256_hex);
let bundle_patch_bytes = std::fs::read(paths.bundle_dir.join(bundle::BUNDLE_PATCH_FILE)).ok();
let bundle_patch_present = bundle_patch_bytes.is_some();
let bundle_patch_sha256 = bundle_patch_bytes.as_deref().map(sha256_hex);
let bundle_profile_bundles = record
.as_ref()
.and_then(|r| r.bundle.as_ref())
.and_then(|b| bundle::profile_bundles(&b.profile_dir));
let (current_identity, current_identity_error) = match current_identity {
Ok(identity) => (Some(map_identity(&identity, allow_full_access)), None),
Err(error) => (None, Some(error)),
};
let shadowing = shadowing_namespaces(&detection);
let state = if !detection.installed() {
DshIntegrationState::NotInstalled
} else {
match &detection.compatibility {
DshCompatibility::Offline { reason } => DshIntegrationState::Offline {
reason: reason.clone(),
},
DshCompatibility::Unparsed { raw } => DshIntegrationState::Offline {
reason: format!("dsh --version printed unparseable text `{raw}`"),
},
DshCompatibility::Incompatible { reason } => DshIntegrationState::Incompatible {
version: detection.version.clone(),
reason: reason.clone(),
},
DshCompatibility::Verified | DshCompatibility::NewerUnverified { .. } => {
let version = detection.version.clone().unwrap_or_default();
match record.as_ref() {
None => DshIntegrationState::Detected { version },
Some(record) if record.disabled => DshIntegrationState::Disabled {
version: Some(version),
},
Some(record) => {
let bundle_stale = record.bundle.as_ref().and_then(|b| {
if !bundle_patch_present {
Some("bundle cordis.patch.yml is missing; run `update`".to_string())
} else if bundle_patch_sha256.as_deref() != Some(b.patch_sha256.as_str()) {
Some("bundle cordis.patch.yml was modified outside Codewhale; run `update`".to_string())
} else if b.patch_sha256 != record.overlay_sha256 {
Some("bundle and overlay identities differ; run `update`".to_string())
} else if !bundle_profile_bundles
.as_ref()
.is_some_and(|list| list.iter().any(|n| n == bundle::BUNDLE_PACKAGE_NAME))
{
Some(format!(
"DSH profile `{}` no longer lists {}; run `remove-bundle` then `install-bundle`",
bundle::BUNDLE_PROFILE,
bundle::BUNDLE_PACKAGE_NAME
))
} else {
None
}
});
let stale_reason = if !overlay_present {
Some("overlay file is missing; run `update`".to_string())
} else if overlay_sha256_on_disk.as_deref()
!= Some(record.overlay_sha256.as_str())
{
Some(
"overlay file was modified outside Codewhale; run `update`"
.to_string(),
)
} else if let Some(reason) = bundle_stale {
Some(reason)
} else {
match current_identity.as_ref() {
Some(now) if !now.mappable() => Some(
"current Codewhale route cannot be carried by DSH; overlay is stale"
.to_string(),
),
Some(now) => {
let expected = render_overlay(now).map(|text| sha256_hex(text.as_bytes()));
match expected {
Some(expected) if expected == record.overlay_sha256 => None,
Some(_) => Some(format!(
"Codewhale route is now {}/{}; overlay pins {}/{}; run `update`",
now.source.provider_id,
now.source.model,
record.identity.source.provider_id,
record.identity.source.model
)),
None => Some(
"current Codewhale route cannot be carried by DSH; overlay is stale"
.to_string(),
),
}
}
None => None,
}
};
match stale_reason {
Some(reason) => DshIntegrationState::StaleConfig { version, reason },
None => match &detection.compatibility {
DshCompatibility::NewerUnverified { verified } => {
DshIntegrationState::StaleVersion {
version,
verified: verified.clone(),
}
}
_ => DshIntegrationState::Connected { version },
},
}
}
}
}
}
};
Ok(DshStatusReport {
state,
detection,
record,
overlay_present,
overlay_sha256_on_disk,
current_identity,
current_identity_error,
shadowing_namespaces: shadowing,
bundle_availability,
bundle_profile_bundles,
bundle_patch_present,
paths_root: paths.root.clone(),
overlay_path: paths.overlay.clone(),
receipt_path: paths.receipt.clone(),
})
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct DshPlan {
pub(crate) mapped: MappedIdentity,
pub(crate) overlay_path: PathBuf,
pub(crate) overlay_text: String,
pub(crate) overlay_sha256: String,
pub(crate) receipt_path: PathBuf,
pub(crate) skin_path: Option<PathBuf>,
pub(crate) profile: String,
pub(crate) launch_command: String,
pub(crate) env_exports: Vec<(String, String)>,
pub(crate) shadowing_namespaces: Vec<String>,
pub(crate) disclosures: Vec<String>,
}
pub(crate) fn plan(
paths: &DshPaths,
detection: &DshDetection,
identity: &CodewhaleRouteIdentity,
profile: &str,
allow_full_access: bool,
skin: bool,
) -> Result<DshPlan> {
let mapped = map_identity(identity, allow_full_access);
let overlay_text = render_overlay(&mapped).ok_or_else(|| match &mapped.adapter {
DshAdapter::Unsupported { reason } => {
anyhow::anyhow!("current Codewhale route cannot be carried by DSH: {reason}")
}
_ => anyhow::anyhow!("overlay could not be rendered"),
})?;
let overlay_sha256 = sha256_hex(overlay_text.as_bytes());
let mut disclosures = mapped.disclosures.clone();
let shadowing = shadowing_namespaces(detection);
if !shadowing.is_empty() {
disclosures.push(format!(
"$DSH_HOME/settings.yaml has [{}] sections; DSH layers those over the overlay per field, so the saved DSH selection can shadow the pinned identity until you clear it in DSH.",
shadowing.join(", ")
));
}
if !detection.profiles.iter().any(|p| p == profile) {
disclosures.push(format!(
"DSH profile `{profile}` is not initialized yet; dsh will create $DSH_HOME/profiles/{profile} on first launch (its own documented behavior)."
));
}
if skin {
disclosures.push(
"Skin: DSH has no custom-theme API; the exported stylesheet is an unsupported overlay you must apply yourself. Nothing is injected."
.to_string(),
);
}
let env_exports = vec![(
"DSH_PERMISSION_MODE".to_string(),
mapped.permission_mode.as_str().to_string(),
)];
let launch_command = format!(
"DSH_PERMISSION_MODE={} dsh --profile {profile} --patch {}",
mapped.permission_mode.as_str(),
paths.overlay.display()
);
Ok(DshPlan {
mapped,
overlay_path: paths.overlay.clone(),
overlay_text,
overlay_sha256,
receipt_path: paths.receipt.clone(),
skin_path: skin.then(|| paths.skin.clone()),
profile: profile.to_string(),
launch_command,
env_exports,
shadowing_namespaces: shadowing,
disclosures,
})
}
fn codewhale_version() -> String {
env!("CARGO_PKG_VERSION").to_string()
}
fn identity_summary(mapped: &MappedIdentity) -> String {
format!(
"{}/{} via {}",
mapped.source.provider_id,
mapped.source.model,
mapped.dsh_provider().unwrap_or("unsupported")
)
}
pub(crate) fn apply_plan(
paths: &DshPaths,
detection: &DshDetection,
plan: &DshPlan,
event: DshReceiptEvent,
) -> Result<DshConnectionRecord> {
std::fs::create_dir_all(&paths.root)
.with_context(|| format!("create {}", paths.root.display()))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let _ = std::fs::set_permissions(&paths.root, std::fs::Permissions::from_mode(0o700));
}
write_atomic(&paths.overlay, plan.overlay_text.as_bytes())?;
let (skin_path, skin_sha256) = if plan.skin_path.is_some() {
let css = skin::skin_css();
write_atomic(&paths.skin, css.as_bytes())?;
write_atomic(&paths.skin_preview, skin::skin_preview_html().as_bytes())?;
(Some(paths.skin.clone()), Some(sha256_hex(css.as_bytes())))
} else {
(None, None)
};
let mut doc = DshReceiptDocument::load(&paths.receipt)?;
let now = now_rfc3339();
let connected_at = doc
.current
.as_ref()
.map(|r| r.connected_at.clone())
.filter(|_| event == DshReceiptEvent::Update)
.unwrap_or_else(|| now.clone());
let bundle_record = match doc.current.as_ref().and_then(|r| r.bundle.clone()) {
Some(mut b) if event == DshReceiptEvent::Update => {
let sha =
bundle::write_bundle(&paths.bundle_dir, &codewhale_version(), &plan.overlay_text)?;
b.patch_sha256 = sha.clone();
b.package_version = bundle::bundle_version(&codewhale_version(), &sha);
b.updated_at = now.clone();
Some(b)
}
_ => None,
};
let record = DshConnectionRecord {
connected_at,
updated_at: now.clone(),
dsh_version: detection.version.clone(),
dsh_binary: detection.binary.clone(),
dsh_home: detection.dsh_home.clone(),
profile: plan.profile.clone(),
overlay_path: paths.overlay.clone(),
overlay_sha256: plan.overlay_sha256.clone(),
skin_enabled: plan.skin_path.is_some(),
skin_path,
skin_sha256: skin_sha256.clone(),
disabled: false,
bundle: bundle_record,
identity: plan.mapped.clone(),
};
doc.push(DshReceiptEntry {
event: event.clone(),
at: now,
codewhale_version: codewhale_version(),
dsh_version: detection.version.clone(),
dsh_home: detection.dsh_home.clone(),
overlay_sha256: Some(plan.overlay_sha256.clone()),
skin_sha256,
identity_summary: Some(identity_summary(&plan.mapped)),
permission_mode: Some(plan.mapped.permission_mode.as_str().to_string()),
note: None,
});
doc.current = Some(record.clone());
doc.save(&paths.receipt)?;
crate::audit::log_sensitive_event(
&format!("integration.dsh.{}", event.as_str()),
serde_json::json!({
"overlay_path": paths.overlay.display().to_string(),
"overlay_sha256": plan.overlay_sha256,
"identity": identity_summary(&plan.mapped),
"permission_mode": plan.mapped.permission_mode.as_str(),
"skin": plan.skin_path.is_some(),
}),
);
Ok(record)
}
pub(crate) fn set_disabled(paths: &DshPaths, disabled: bool) -> Result<DshConnectionRecord> {
let mut doc = DshReceiptDocument::load(&paths.receipt)?;
let Some(mut record) = doc.current.take() else {
anyhow::bail!(
"DSH is not connected; nothing to {}",
if disabled { "disable" } else { "enable" }
);
};
record.disabled = disabled;
record.updated_at = now_rfc3339();
let event = if disabled {
DshReceiptEvent::Disable
} else {
DshReceiptEvent::Enable
};
doc.push(DshReceiptEntry {
event: event.clone(),
at: record.updated_at.clone(),
codewhale_version: codewhale_version(),
dsh_version: record.dsh_version.clone(),
dsh_home: record.dsh_home.clone(),
overlay_sha256: Some(record.overlay_sha256.clone()),
skin_sha256: record.skin_sha256.clone(),
identity_summary: Some(identity_summary(&record.identity)),
permission_mode: Some(record.identity.permission_mode.as_str().to_string()),
note: None,
});
doc.current = Some(record.clone());
doc.save(&paths.receipt)?;
crate::audit::log_sensitive_event(
&format!("integration.dsh.{}", event.as_str()),
serde_json::json!({ "overlay_path": paths.overlay.display().to_string() }),
);
Ok(record)
}
pub(crate) fn remove(paths: &DshPaths) -> Result<Vec<PathBuf>> {
let mut doc = DshReceiptDocument::load(&paths.receipt)?;
if doc.current.as_ref().is_some_and(|r| r.bundle.is_some()) {
anyhow::bail!(
"the Codewhale bundle is still installed in DSH profile `{}`; run `{CLI_COMMAND} remove-bundle` first",
bundle::BUNDLE_PROFILE
);
}
let mut removed = Vec::new();
for path in [&paths.overlay, &paths.skin, &paths.skin_preview] {
match std::fs::remove_file(path) {
Ok(()) => removed.push(path.clone()),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Err(error) => return Err(error).with_context(|| format!("remove {}", path.display())),
}
}
let previous = doc.current.take();
let now = now_rfc3339();
doc.push(DshReceiptEntry {
event: DshReceiptEvent::Remove,
at: now,
codewhale_version: codewhale_version(),
dsh_version: previous.as_ref().and_then(|r| r.dsh_version.clone()),
dsh_home: previous
.as_ref()
.map(|r| r.dsh_home.clone())
.unwrap_or_default(),
overlay_sha256: previous.as_ref().map(|r| r.overlay_sha256.clone()),
skin_sha256: previous.as_ref().and_then(|r| r.skin_sha256.clone()),
identity_summary: previous.as_ref().map(|r| identity_summary(&r.identity)),
permission_mode: previous
.as_ref()
.map(|r| r.identity.permission_mode.as_str().to_string()),
note: Some(format!(
"removed {} Codewhale-owned file(s); $DSH_HOME untouched",
removed.len()
)),
});
doc.save(&paths.receipt)?;
crate::audit::log_sensitive_event(
"integration.dsh.remove",
serde_json::json!({ "removed": removed.iter().map(|p| p.display().to_string()).collect::<Vec<_>>() }),
);
Ok(removed)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct LaunchSpec {
pub(crate) binary: PathBuf,
pub(crate) args: Vec<String>,
pub(crate) env: Vec<(String, String)>,
pub(crate) strip_env: Vec<String>,
pub(crate) cwd: PathBuf,
}
pub(crate) fn launch_env_strip_list(
api_key_source: Option<&str>,
provider_env_vars: &[String],
) -> Vec<String> {
let mut out = vec![
"CODEWHALE_CLI_API_KEY".to_string(),
"DEEPSEEK_API_KEY_SOURCE".to_string(),
];
if matches!(api_key_source, Some("cli" | "keyring")) {
out.push("DEEPSEEK_API_KEY".to_string());
for var in provider_env_vars {
if !out.contains(var) {
out.push(var.clone());
}
}
}
out
}
impl LaunchSpec {
pub(crate) fn display(&self) -> String {
let mut out = String::new();
for (k, v) in &self.env {
out.push_str(&format!("{k}={v} "));
}
out.push_str(&self.binary.display().to_string());
for arg in &self.args {
out.push(' ');
out.push_str(arg);
}
out
}
}
pub(crate) fn launch_spec(
report: &DshStatusReport,
profile_override: Option<&str>,
extra_args: &[String],
workspace: &Path,
) -> Result<LaunchSpec> {
let record = report.record.as_ref().ok_or_else(|| {
anyhow::anyhow!("DSH is not connected; run `{CLI_COMMAND} connect` first")
})?;
if !report.state.launchable() {
match &report.state {
DshIntegrationState::Connected { .. } | DshIntegrationState::StaleVersion { .. } => {}
DshIntegrationState::Disabled { .. } => {
anyhow::bail!(
"DSH integration is disabled; run `{CLI_COMMAND} enable` to launch again"
)
}
DshIntegrationState::StaleConfig { reason, .. } => {
anyhow::bail!(
"DSH overlay is stale ({reason}); run `{CLI_COMMAND} update` before launching"
)
}
DshIntegrationState::Incompatible { reason, .. } => {
anyhow::bail!("installed dsh is incompatible: {reason}")
}
DshIntegrationState::Offline { reason } => anyhow::bail!("dsh is offline: {reason}"),
DshIntegrationState::NotInstalled => anyhow::bail!("dsh is not installed"),
DshIntegrationState::Detected { .. } => {
anyhow::bail!("DSH is detected but not connected; run `{CLI_COMMAND} connect`")
}
}
}
let binary = report
.detection
.binary
.clone()
.ok_or_else(|| anyhow::anyhow!("dsh binary path is unknown"))?;
let bundle_installed = record.bundle.is_some();
let profile = profile_override.unwrap_or(if bundle_installed {
bundle::BUNDLE_PROFILE
} else {
record.profile.as_str()
});
if !matches!(profile, "web" | "headless") && profile != bundle::BUNDLE_PROFILE {
anyhow::bail!(
"DSH profile must be `web`, `headless`, or `{}` (bundle), got `{profile}`",
bundle::BUNDLE_PROFILE
);
}
if profile == bundle::BUNDLE_PROFILE && !bundle_installed {
anyhow::bail!(
"the `{}` profile carries identity only after `{CLI_COMMAND} install-bundle`",
bundle::BUNDLE_PROFILE
);
}
let mut args = vec!["--profile".to_string(), profile.to_string()];
if profile != bundle::BUNDLE_PROFILE {
args.push("--patch".to_string());
args.push(report.overlay_path.display().to_string());
}
args.extend(extra_args.iter().cloned());
let provider_env_vars: Vec<String> =
record.identity.source.api_key_env.iter().cloned().collect();
let strip_env = launch_env_strip_list(
std::env::var("DEEPSEEK_API_KEY_SOURCE").ok().as_deref(),
&provider_env_vars,
);
Ok(LaunchSpec {
binary,
args,
env: vec![(
"DSH_PERMISSION_MODE".to_string(),
record.identity.permission_mode.as_str().to_string(),
)],
strip_env,
cwd: workspace.to_path_buf(),
})
}
pub(crate) fn spawn_launch(spec: &LaunchSpec) -> Result<i32> {
let mut command = std::process::Command::new(&spec.binary);
command.args(&spec.args).current_dir(&spec.cwd);
for name in &spec.strip_env {
command.env_remove(name);
}
for (k, v) in &spec.env {
command.env(k, v);
}
let status = command
.status()
.with_context(|| format!("launch {}", spec.binary.display()))?;
Ok(status.code().unwrap_or(1))
}
pub(crate) fn install_bundle(
paths: &DshPaths,
detection: &DshDetection,
runner: &dyn DshRunner,
availability: &BundleAvailability,
app: DshAppBundle,
) -> Result<DshBundleRecord> {
let pnpm_version = match availability {
BundleAvailability::Available { pnpm_version } => pnpm_version.clone(),
BundleAvailability::NotAvailable { reason } => {
anyhow::bail!("DSH plugin path not available: {reason}")
}
};
let mut doc = DshReceiptDocument::load(&paths.receipt)?;
let Some(mut record) = doc.current.take() else {
anyhow::bail!("DSH is not connected; run `{CLI_COMMAND} connect` first");
};
if record.bundle.is_some() {
anyhow::bail!("bundle already installed; use `{CLI_COMMAND} update` or `remove-bundle`");
}
let overlay_text = std::fs::read_to_string(&paths.overlay)
.with_context(|| format!("read {}", paths.overlay.display()))?;
if sha256_hex(overlay_text.as_bytes()) != record.overlay_sha256 {
anyhow::bail!("overlay is stale; run `{CLI_COMMAND} update` before install-bundle");
}
let patch_sha = bundle::write_bundle(&paths.bundle_dir, &codewhale_version(), &overlay_text)?;
let (app_source, outcomes) =
match bundle::install_into_profile(runner, detection, app, &paths.bundle_dir) {
Ok(ok) => ok,
Err(error) => {
let _ = bundle::remove_bundle_files(&paths.bundle_dir);
return Err(error);
}
};
let now = now_rfc3339();
let mut digest_input = String::new();
for outcome in &outcomes {
digest_input.push_str(&outcome.output_sha256);
digest_input.push('\n');
}
let bundle_record = DshBundleRecord {
installed_at: now.clone(),
updated_at: now.clone(),
profile: bundle::BUNDLE_PROFILE.to_string(),
profile_dir: detection
.dsh_home
.join("profiles")
.join(bundle::BUNDLE_PROFILE),
bundle_dir: paths.bundle_dir.clone(),
package_name: bundle::BUNDLE_PACKAGE_NAME.to_string(),
package_version: bundle::bundle_version(&codewhale_version(), &patch_sha),
patch_sha256: patch_sha.clone(),
app_bundle: app,
app_bundle_source: app_source,
pnpm_version,
pnpm_output_sha256: sha256_hex(digest_input.as_bytes()),
};
record.bundle = Some(bundle_record.clone());
record.updated_at = now.clone();
doc.push(DshReceiptEntry {
event: DshReceiptEvent::InstallBundle,
at: now,
codewhale_version: codewhale_version(),
dsh_version: detection.version.clone(),
dsh_home: detection.dsh_home.clone(),
overlay_sha256: Some(record.overlay_sha256.clone()),
skin_sha256: record.skin_sha256.clone(),
identity_summary: Some(identity_summary(&record.identity)),
permission_mode: Some(record.identity.permission_mode.as_str().to_string()),
note: Some(format!(
"dsh plugin --profile {} add {} + {}; pnpm {}; output sha256 {}",
bundle::BUNDLE_PROFILE,
app.package_name(),
bundle::BUNDLE_PACKAGE_NAME,
bundle_record.pnpm_version,
bundle_record.pnpm_output_sha256
)),
});
doc.current = Some(record);
doc.save(&paths.receipt)?;
crate::audit::log_sensitive_event(
"integration.dsh.install_bundle",
serde_json::json!({
"profile_dir": bundle_record.profile_dir.display().to_string(),
"bundle_dir": paths.bundle_dir.display().to_string(),
"patch_sha256": bundle_record.patch_sha256,
"app_bundle": app.package_name(),
}),
);
Ok(bundle_record)
}
pub(crate) fn remove_bundle(
paths: &DshPaths,
detection: &DshDetection,
runner: &dyn DshRunner,
) -> Result<Vec<PathBuf>> {
let mut doc = DshReceiptDocument::load(&paths.receipt)?;
let Some(mut record) = doc.current.take() else {
anyhow::bail!("DSH is not connected; nothing to remove");
};
let Some(bundle_record) = record.bundle.take() else {
anyhow::bail!("no bundle is installed");
};
let outcome = bundle::remove_from_profile(runner, detection)?;
let removed = bundle::remove_bundle_files(&paths.bundle_dir)?;
let now = now_rfc3339();
record.updated_at = now.clone();
doc.push(DshReceiptEntry {
event: DshReceiptEvent::RemoveBundle,
at: now,
codewhale_version: codewhale_version(),
dsh_version: detection.version.clone(),
dsh_home: detection.dsh_home.clone(),
overlay_sha256: Some(record.overlay_sha256.clone()),
skin_sha256: record.skin_sha256.clone(),
identity_summary: Some(identity_summary(&record.identity)),
permission_mode: Some(record.identity.permission_mode.as_str().to_string()),
note: Some(format!(
"dsh plugin --profile {} remove {} (output sha256 {}); removed {} owned file(s); profile dir {} left in place (DSH-owned)",
bundle::BUNDLE_PROFILE,
bundle::BUNDLE_PACKAGE_NAME,
outcome.output_sha256,
removed.len(),
bundle_record.profile_dir.display()
)),
});
doc.current = Some(record);
doc.save(&paths.receipt)?;
crate::audit::log_sensitive_event(
"integration.dsh.remove_bundle",
serde_json::json!({ "removed": removed.iter().map(|p| p.display().to_string()).collect::<Vec<_>>() }),
);
Ok(removed)
}
pub(crate) fn bundle_availability_now() -> BundleAvailability {
bundle::bundle_availability(std::env::var_os("PATH").as_ref(), &ProcessRunner)
}
pub(crate) fn codewhale_route_identity(
config: &crate::config::Config,
workspace: &Path,
) -> Result<CodewhaleRouteIdentity, String> {
let provider = config.api_provider();
let configured_model = config.default_model();
let route =
crate::route_runtime::resolve_runtime_route(config, provider, Some(&configured_model))?;
let candidate = &route.candidate;
let base_url = candidate.endpoint().base_url.clone();
let protocol = match candidate.protocol() {
codewhale_config::provider::WireFormat::ChatCompletions => WireProtocol::ChatCompletions,
codewhale_config::provider::WireFormat::Responses => WireProtocol::Responses,
codewhale_config::provider::WireFormat::AnthropicMessages => {
WireProtocol::AnthropicMessages
}
};
let keyless_local = crate::config::provider_route_is_keyless_self_hosted(provider, &base_url);
let api_key_env = provider.env_vars().first().map(|s| (*s).to_string());
Ok(CodewhaleRouteIdentity {
provider_id: candidate.provider_id().as_str().to_string(),
provider_label: provider.display_name().to_string(),
model: candidate.wire_model_id().as_str().to_string(),
base_url,
protocol,
api_key_env,
keyless_local,
reasoning_effort: config.reasoning_effort().map(str::to_string),
sandbox_mode: config.sandbox_mode.clone(),
approval_policy: config.approval_policy.clone(),
yolo: config.yolo.unwrap_or(false),
workspace: workspace.display().to_string(),
})
}
pub(crate) fn status_line(report: &DshStatusReport) -> String {
let version = report.detection.version.as_deref().unwrap_or("?");
match &report.state {
DshIntegrationState::NotInstalled => {
format!("not installed — `dsh` not on PATH; connect later with `{CLI_COMMAND} connect`")
}
DshIntegrationState::Offline { reason } => format!("offline — {reason}"),
DshIntegrationState::Incompatible { reason, .. } => {
format!("incompatible — dsh {version}: {reason}")
}
DshIntegrationState::Detected { .. } => format!(
"detected — dsh {version}, not connected; `{CLI_COMMAND} plan` explains what would be written"
),
DshIntegrationState::Connected { .. } => {
let identity = report
.record
.as_ref()
.map(|r| identity_summary(&r.identity))
.unwrap_or_default();
let bundle = report
.record
.as_ref()
.and_then(|r| r.bundle.as_ref())
.map(|b| format!(", bundle in profile `{}`", b.profile))
.unwrap_or_default();
format!("connected — dsh {version}, {identity}{bundle} ({RELATIONSHIP_LABEL})")
}
DshIntegrationState::StaleConfig { reason, .. } => {
format!("stale-config — dsh {version}: {reason}")
}
DshIntegrationState::StaleVersion { verified, .. } => format!(
"stale-version — dsh {version} is newer than verified {verified}; connected but unverified"
),
DshIntegrationState::Disabled { .. } => {
format!("disabled — overlay kept, launches refused; `{CLI_COMMAND} enable`")
}
}
}