swarm repositories / source
aboutsummaryrefslogtreecommitdiff
path: root/crates/phone-opus/src/mcp
diff options
context:
space:
mode:
authormain <main@swarm.moe>2026-03-25 00:43:57 -0400
committermain <main@swarm.moe>2026-03-25 00:43:57 -0400
commit2c219204d627634442d46c38d1b5df806f77f4c1 (patch)
tree02b6c0108a698a422aef88ed8bb7e602212bd36d /crates/phone-opus/src/mcp
parent481aaa4ee150671d86655d566f52aa1bd7254c16 (diff)
downloadphone_opus-2c219204d627634442d46c38d1b5df806f77f4c1.zip
Disable public resume behavior
Diffstat (limited to 'crates/phone-opus/src/mcp')
-rw-r--r--crates/phone-opus/src/mcp/catalog.rs6
-rw-r--r--crates/phone-opus/src/mcp/fault.rs27
-rw-r--r--crates/phone-opus/src/mcp/service.rs183
3 files changed, 64 insertions, 152 deletions
diff --git a/crates/phone-opus/src/mcp/catalog.rs b/crates/phone-opus/src/mcp/catalog.rs
index f17a3c5..cf18fc3 100644
--- a/crates/phone-opus/src/mcp/catalog.rs
+++ b/crates/phone-opus/src/mcp/catalog.rs
@@ -41,7 +41,7 @@ impl ToolSpec {
const TOOL_SPECS: &[ToolSpec] = &[
ToolSpec {
name: "consult",
- description: "Run a blocking consult against the system Claude Code install using a read-only built-in toolset, automatically reuse the remembered context for the current cwd by default, optionally opt out with fresh_context, and return the response plus execution metadata.",
+ description: "Run a blocking one-shot consult against the system Claude Code install using a read-only built-in toolset and return the response plus execution metadata.",
dispatch: DispatchTarget::Worker,
replay: ReplayContract::NeverReplay,
},
@@ -89,10 +89,6 @@ fn tool_schema(name: &str) -> Value {
"cwd": {
"type": "string",
"description": "Optional working directory for the Claude Code session. Relative paths resolve against the MCP host working directory."
- },
- "fresh_context": {
- "type": "boolean",
- "description": "When true, start a fresh Claude context instead of reusing the remembered context for this cwd. Defaults to false."
}
},
"required": ["prompt"]
diff --git a/crates/phone-opus/src/mcp/fault.rs b/crates/phone-opus/src/mcp/fault.rs
index c2d4a6c..b0b1e28 100644
--- a/crates/phone-opus/src/mcp/fault.rs
+++ b/crates/phone-opus/src/mcp/fault.rs
@@ -13,14 +13,6 @@ pub(crate) struct FaultContext {
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub(crate) struct ConsultFaultContext {
pub(crate) cwd: String,
- pub(crate) context_mode: String,
- pub(crate) planned_session_id: String,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub(crate) reused_session_id: Option<String>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub(crate) observed_session_id: Option<String>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub(crate) resume_session_id: Option<String>,
#[serde(default, skip_serializing_if = "is_false")]
pub(crate) quota_limited: bool,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -200,23 +192,8 @@ impl FaultRecord {
else {
return lines.join("\n");
};
- let mut fields: BTreeMap<String, String> = BTreeMap::from([
- ("cwd".to_owned(), consult.cwd.clone()),
- ("context_mode".to_owned(), consult.context_mode.clone()),
- (
- "planned_session".to_owned(),
- consult.planned_session_id.clone(),
- ),
- ]);
- if let Some(session_id) = consult.reused_session_id.as_ref() {
- let _ = fields.insert("reused_session".to_owned(), session_id.clone());
- }
- if let Some(session_id) = consult.observed_session_id.as_ref() {
- let _ = fields.insert("observed_session".to_owned(), session_id.clone());
- }
- if let Some(session_id) = consult.resume_session_id.as_ref() {
- let _ = fields.insert("resume_session".to_owned(), session_id.clone());
- }
+ let mut fields: BTreeMap<String, String> =
+ BTreeMap::from([("cwd".to_owned(), consult.cwd.clone())]);
if consult.quota_limited {
let _ = fields.insert("quota_limited".to_owned(), "true".to_owned());
}
diff --git a/crates/phone-opus/src/mcp/service.rs b/crates/phone-opus/src/mcp/service.rs
index bb56ad3..e152fa7 100644
--- a/crates/phone-opus/src/mcp/service.rs
+++ b/crates/phone-opus/src/mcp/service.rs
@@ -156,14 +156,7 @@ impl ConsultRequest {
let cwd = WorkingDirectory::resolve(args.cwd)?;
let context_key = ConsultContextKey::from_cwd(&cwd);
let fresh_context = args.fresh_context.unwrap_or(false);
- let session_plan = if fresh_context {
- ConsultSessionPlan::fresh()
- } else {
- load_consult_context(&context_key)
- .map_err(|source| ConsultRequestError::ContextIndex { source })?
- .and_then(ConsultSessionPlan::from_stored)
- .unwrap_or_else(ConsultSessionPlan::fresh)
- };
+ let session_plan = ConsultSessionPlan::fresh();
Ok(Self {
prompt,
cwd,
@@ -173,6 +166,10 @@ impl ConsultRequest {
})
}
+ fn planned_session_id(&self) -> String {
+ self.session_plan.planned_session().display()
+ }
+
fn context_mode(&self) -> &'static str {
self.session_plan.context_mode()
}
@@ -181,16 +178,6 @@ impl ConsultRequest {
self.session_plan.reused_session_id()
}
- fn planned_session_id(&self) -> String {
- self.session_plan.planned_session().display()
- }
-
- fn launch_resume_session(&self) -> Option<String> {
- self.session_plan
- .resume_session()
- .map(SessionHandle::display)
- }
-
fn launch_session_id(&self) -> Option<String> {
match self.session_plan {
ConsultSessionPlan::Start { .. } => Some(self.planned_session_id()),
@@ -214,14 +201,6 @@ impl ConsultRequest {
}
}
- fn current_context_session_id(&self) -> Option<String> {
- load_consult_context(&self.context_key)
- .ok()
- .flatten()
- .and_then(ConsultSessionPlan::from_stored)
- .map(|plan| plan.planned_session().display())
- }
-
#[allow(dead_code, reason = "background submission is parked but not exposed")]
fn background_request(&self) -> BackgroundConsultRequest {
BackgroundConsultRequest {
@@ -351,6 +330,10 @@ struct ConsultContextIndex {
}
impl ConsultContextIndex {
+ #[allow(
+ dead_code,
+ reason = "context lookup is parked while session reuse stays disabled"
+ )]
fn context_for(&self, key: &ConsultContextKey) -> Option<StoredConsultContext> {
self.by_cwd.get(key.as_str()).cloned()
}
@@ -378,6 +361,10 @@ enum ConsultSessionPlan {
session: SessionHandle,
reused: bool,
},
+ #[allow(
+ dead_code,
+ reason = "resume plans are parked while one-shot consults are enforced"
+ )]
Resume(SessionHandle),
}
@@ -389,6 +376,10 @@ impl ConsultSessionPlan {
}
}
+ #[allow(
+ dead_code,
+ reason = "stored-session revival is parked while one-shot consults are enforced"
+ )]
fn from_stored(context: StoredConsultContext) -> Option<Self> {
let session = SessionHandle::parse(context.session_id.as_str())?;
Some(match context.state {
@@ -406,6 +397,10 @@ impl ConsultSessionPlan {
}
}
+ #[allow(
+ dead_code,
+ reason = "resume paths are parked while one-shot consults are enforced"
+ )]
fn resume_session(&self) -> Option<&SessionHandle> {
match self {
Self::Resume(session) => Some(session),
@@ -647,6 +642,10 @@ enum ConsultRequestError {
Canonicalize { path: String, source: io::Error },
#[error("working directory `{0}` is not a directory")]
NotDirectory(String),
+ #[allow(
+ dead_code,
+ reason = "context index loading is parked while one-shot consults are enforced"
+ )]
#[error("failed to resolve consult context state: {source}")]
ContextIndex { source: io::Error },
#[error("job_id must be a valid UUID, got `{0}`")]
@@ -705,14 +704,30 @@ struct ConsultResponse {
cwd: WorkingDirectory,
result: String,
persisted_output_path: PersistedConsultPath,
+ #[allow(
+ dead_code,
+ reason = "session metadata is retained internally but hidden from the public surface"
+ )]
context_mode: &'static str,
+ #[allow(
+ dead_code,
+ reason = "session metadata is retained internally but hidden from the public surface"
+ )]
planned_session_id: String,
+ #[allow(
+ dead_code,
+ reason = "session metadata is retained internally but hidden from the public surface"
+ )]
reused_session_id: Option<String>,
duration_ms: u64,
duration_api_ms: Option<u64>,
num_turns: u64,
stop_reason: Option<String>,
session_id: Option<String>,
+ #[allow(
+ dead_code,
+ reason = "session metadata is retained internally but hidden from the public surface"
+ )]
observed_session_id: Option<String>,
total_cost_usd: Option<f64>,
usage: Option<Value>,
@@ -932,18 +947,14 @@ impl ClaudeSandbox {
struct PersistedConsultPath(PathBuf);
impl PersistedConsultPath {
- fn new(request: &ConsultRequest, session_id: Option<&str>) -> io::Result<Self> {
+ fn new(request: &ConsultRequest) -> io::Result<Self> {
fs::create_dir_all(CONSULT_OUTPUT_ROOT)?;
let timestamp = OffsetDateTime::now_utc()
.format(CONSULT_TIMESTAMP_FORMAT)
.map_err(|error| io::Error::other(error.to_string()))?;
let slug = consult_slug(request.prompt.as_str());
- let session_slug = session_id.map_or_else(
- || "session-none".to_owned(),
- |session_id| format!("session-{}", consult_slug(session_id)),
- );
Ok(Self(Path::new(CONSULT_OUTPUT_ROOT).join(format!(
- "{timestamp}-{slug}-{session_slug}-{}.json",
+ "{timestamp}-{slug}-{}.json",
Uuid::new_v4()
))))
}
@@ -1010,27 +1021,12 @@ fn consult_fault_context(request: &ConsultRequest, error: &ConsultInvocationErro
| ConsultInvocationError::Stalled(detail)
| ConsultInvocationError::Downstream(detail) => Some(detail.as_str()),
};
- let reused_session_id = request.reused_session_id();
- let planned_session_id = request.planned_session_id();
- let observed_session_id = detail
- .and_then(downstream_session_id)
- .clone()
- .or_else(|| request.current_context_session_id());
- let resume_session_id = observed_session_id
- .clone()
- .or_else(|| reused_session_id.clone())
- .or_else(|| Some(planned_session_id.clone()));
let quota_reset_hint = detail.and_then(quota_reset_hint);
let quota_limited = quota_reset_hint.is_some();
- let retry_hint = consult_retry_hint(quota_limited, resume_session_id.as_deref());
+ let retry_hint = consult_retry_hint(quota_limited, error);
FaultContext {
consult: Some(ConsultFaultContext {
cwd: request.cwd.display(),
- context_mode: request.context_mode().to_owned(),
- planned_session_id,
- reused_session_id,
- observed_session_id,
- resume_session_id,
quota_limited,
quota_reset_hint,
retry_hint,
@@ -1038,34 +1034,23 @@ fn consult_fault_context(request: &ConsultRequest, error: &ConsultInvocationErro
}
}
-fn downstream_session_id(detail: &str) -> Option<String> {
- let value = serde_json::from_str::<Value>(detail).ok()?;
- let session_id = value.get("session_id")?.as_str()?;
- SessionHandle::parse(session_id).map(|session| session.display())
-}
-
fn quota_reset_hint(detail: &str) -> Option<String> {
let (_, suffix) = detail.split_once("resets ")?;
let hint = suffix.trim();
(!hint.is_empty()).then(|| hint.to_owned())
}
-fn consult_retry_hint(quota_limited: bool, resume_session_id: Option<&str>) -> Option<String> {
+fn consult_retry_hint(quota_limited: bool, error: &ConsultInvocationError) -> Option<String> {
if quota_limited {
- return Some(match resume_session_id {
- Some(session_id) => format!(
- "wait for the quota window to reset, then retry consult on the same cwd; phone_opus will reuse resume_session {session_id} automatically"
- ),
- None => {
- "wait for the quota window to reset, then retry consult on the same cwd".to_owned()
- }
- });
+ return Some("wait for the quota window to reset, then retry the consult".to_owned());
+ }
+ match error {
+ ConsultInvocationError::Stalled(_) => Some(
+ "Claude stalled before producing output; retry the consult as a fresh one-shot call"
+ .to_owned(),
+ ),
+ _ => None,
}
- resume_session_id.map(|session_id| {
- format!(
- "retry consult on the same cwd; phone_opus will reuse resume_session {session_id} automatically"
- )
- })
}
pub(crate) fn consult_job_tool_output(
@@ -1502,9 +1487,6 @@ fn invoke_claude(request: &ConsultRequest) -> Result<ConsultResponse, ConsultInv
if let Some(session_id) = request.launch_session_id() {
let _ = command.arg("--session-id").arg(session_id);
}
- if let Some(session_id) = request.launch_resume_session() {
- let _ = command.arg("--resume").arg(session_id);
- }
let mut child = command
.arg(request.prompt.rendered())
.spawn()
@@ -1577,14 +1559,8 @@ fn invoke_claude(request: &ConsultRequest) -> Result<ConsultResponse, ConsultInv
request
.remember_context(canonical_session_id.as_deref())
.map_err(ConsultInvocationError::Spawn)?;
- let persisted_output_path = persist_consult_output(
- request,
- &result,
- &envelope,
- canonical_session_id.as_deref(),
- observed_session_id.as_deref(),
- )
- .map_err(ConsultInvocationError::Spawn)?;
+ let persisted_output_path = persist_consult_output(request, &result, &envelope)
+ .map_err(ConsultInvocationError::Spawn)?;
Ok(ConsultResponse {
cwd: request.cwd.clone(),
result,
@@ -1796,6 +1772,10 @@ fn load_consult_context_index() -> io::Result<ConsultContextIndex> {
}
}
+#[allow(
+ dead_code,
+ reason = "context lookup is parked while one-shot consults are enforced"
+)]
fn load_consult_context(key: &ConsultContextKey) -> io::Result<Option<StoredConsultContext>> {
Ok(load_consult_context_index()?.context_for(key))
}
@@ -1886,10 +1866,8 @@ fn persist_consult_output(
request: &ConsultRequest,
result: &str,
envelope: &ClaudeJsonEnvelope,
- session_id: Option<&str>,
- observed_session_id: Option<&str>,
) -> io::Result<PersistedConsultPath> {
- let path = PersistedConsultPath::new(request, session_id)?;
+ let path = PersistedConsultPath::new(request)?;
let saved_at = OffsetDateTime::now_utc()
.format(&Rfc3339)
.map_err(|error| io::Error::other(error.to_string()))?;
@@ -1901,17 +1879,12 @@ fn persist_consult_output(
"prompt": request.prompt.as_str(),
"prompt_prefix": CLAUDE_CONSULT_PREFIX,
"effective_prompt": request.prompt.rendered(),
- "context_mode": request.context_mode(),
- "planned_session_id": request.planned_session_id(),
- "reused_session_id": request.reused_session_id(),
"response": result,
"model": model_name(envelope.model_usage.as_ref()),
"duration_ms": envelope.duration_ms.unwrap_or(0),
"duration_api_ms": envelope.duration_api_ms,
"num_turns": envelope.num_turns.unwrap_or(0),
"stop_reason": envelope.stop_reason,
- "session_id": session_id,
- "observed_session_id": observed_session_id,
"total_cost_usd": envelope.total_cost_usd,
"usage": envelope.usage,
"model_usage": envelope.model_usage,
@@ -2002,16 +1975,11 @@ fn consult_output(
"response": response.result,
"cwd": response.cwd.display(),
"persisted_output_path": response.persisted_output_path.display(),
- "context_mode": response.context_mode,
- "planned_session_id": response.planned_session_id,
- "reused_session_id": response.reused_session_id,
"prompt_prefix_injected": true,
"model": response.model_name(),
"duration_ms": response.duration_ms,
"num_turns": response.num_turns,
"stop_reason": response.stop_reason,
- "session_id": response.session_id,
- "observed_session_id": response.observed_session_id,
"total_cost_usd": response.total_cost_usd,
"permission_denial_count": response.permission_denials.len(),
});
@@ -2022,15 +1990,10 @@ fn consult_output(
"prompt": request.prompt.as_str(),
"prompt_prefix": CLAUDE_CONSULT_PREFIX,
"effective_prompt": request.prompt.rendered(),
- "context_mode": response.context_mode,
- "planned_session_id": response.planned_session_id,
- "reused_session_id": response.reused_session_id,
"duration_ms": response.duration_ms,
"duration_api_ms": response.duration_api_ms,
"num_turns": response.num_turns,
"stop_reason": response.stop_reason,
- "session_id": response.session_id,
- "observed_session_id": response.observed_session_id,
"total_cost_usd": response.total_cost_usd,
"usage": response.usage,
"model_usage": response.model_usage,
@@ -2053,7 +2016,6 @@ fn consult_output(
fn concise_text(_request: &ConsultRequest, response: &ConsultResponse) -> String {
let mut status = vec![
"consult ok".to_owned(),
- format!("context={}", response.context_mode),
format!("turns={}", response.num_turns),
format!("duration={}", render_duration_ms(response.duration_ms)),
];
@@ -2069,16 +2031,6 @@ fn concise_text(_request: &ConsultRequest, response: &ConsultResponse) -> String
let mut lines = vec![status.join(" ")];
lines.push(format!("cwd: {}", response.cwd.display()));
- lines.push(format!("planned_session: {}", response.planned_session_id));
- if let Some(session_id) = response.reused_session_id.as_deref() {
- lines.push(format!("reused_session: {session_id}"));
- }
- if let Some(session_id) = response.observed_session_id.as_deref() {
- lines.push(format!("observed_session: {session_id}"));
- }
- if let Some(session_id) = response.session_id.as_deref() {
- lines.push(format!("session: {session_id}"));
- }
lines.push(format!(
"saved: {}",
response.persisted_output_path.display()
@@ -2096,20 +2048,10 @@ fn concise_text(_request: &ConsultRequest, response: &ConsultResponse) -> String
fn full_text(_request: &ConsultRequest, response: &ConsultResponse) -> String {
let mut lines = vec![
- format!(
- "consult ok context={} turns={}",
- response.context_mode, response.num_turns
- ),
+ format!("consult ok turns={}", response.num_turns),
format!("cwd: {}", response.cwd.display()),
- format!("planned_session: {}", response.planned_session_id),
format!("duration: {}", render_duration_ms(response.duration_ms)),
];
- if let Some(session_id) = response.reused_session_id.as_deref() {
- lines.push(format!("reused_session: {session_id}"));
- }
- if let Some(session_id) = response.observed_session_id.as_deref() {
- lines.push(format!("observed_session: {session_id}"));
- }
if let Some(duration_api_ms) = response.duration_api_ms {
lines.push(format!(
"api_duration: {}",
@@ -2122,9 +2064,6 @@ fn full_text(_request: &ConsultRequest, response: &ConsultResponse) -> String {
if let Some(stop_reason) = response.stop_reason.as_deref() {
lines.push(format!("stop: {stop_reason}"));
}
- if let Some(session_id) = response.session_id.as_deref() {
- lines.push(format!("session: {session_id}"));
- }
lines.push(format!(
"saved: {}",
response.persisted_output_path.display()