summaryrefslogtreecommitdiff
path: root/makima/src
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-03-04 16:47:12 +0000
committerGitHub <noreply@github.com>2026-03-04 16:47:12 +0000
commitec9738a069e61529be040eff065318972b8a11e2 (patch)
treed1b15d3b22d4980acff4fba8a12b99920035025c /makima/src
parent78cb861412850889424ae7d5ae5cd952a2b90295 (diff)
downloadsoryu-ec9738a069e61529be040eff065318972b8a11e2.tar.gz
soryu-ec9738a069e61529be040eff065318972b8a11e2.zip
feat: task slide-out panel, 3-way reconcile toggle, daemon reauth fix (#85)
* WIP: heartbeat checkpoint * WIP: heartbeat checkpoint * feat: soryu-co/soryu - makima: Fix daemon reauth flow for new claude setup-token output format * feat: soryu-co/soryu - makima: Update frontend reconcile toggle to three-way switch * feat: soryu-co/soryu - makima: Add task slide-out panel to directive page
Diffstat (limited to 'makima/src')
-rw-r--r--makima/src/daemon/process/claude.rs10
-rw-r--r--makima/src/daemon/task/manager.rs224
-rw-r--r--makima/src/daemon/ws/protocol.rs5
-rw-r--r--makima/src/db/models.rs16
-rw-r--r--makima/src/db/repository.rs9
-rw-r--r--makima/src/orchestration/directive.rs21
-rw-r--r--makima/src/server/handlers/mesh_daemon.rs8
-rw-r--r--makima/src/server/handlers/mesh_supervisor.rs16
8 files changed, 211 insertions, 98 deletions
diff --git a/makima/src/daemon/process/claude.rs b/makima/src/daemon/process/claude.rs
index c8add1c..57c8f77 100644
--- a/makima/src/daemon/process/claude.rs
+++ b/makima/src/daemon/process/claude.rs
@@ -510,6 +510,16 @@ impl ProcessManager {
env.extend(extra);
}
+ // Load OAuth token from disk and set as env var if not already provided.
+ // This allows processes to authenticate using tokens saved by the reauth flow.
+ // The token is loaded fresh each time (not cached) so newly saved tokens are picked up.
+ if !env.contains_key("CLAUDE_CODE_OAUTH_TOKEN") {
+ if let Some(token) = crate::daemon::task::manager::load_oauth_token() {
+ tracing::debug!("Setting CLAUDE_CODE_OAUTH_TOKEN from saved token file");
+ env.insert("CLAUDE_CODE_OAUTH_TOKEN".to_string(), token);
+ }
+ }
+
// Build Claude arguments list
let mut claude_args = Vec::new();
diff --git a/makima/src/daemon/task/manager.rs b/makima/src/daemon/task/manager.rs
index addcd71..df5e167 100644
--- a/makima/src/daemon/task/manager.rs
+++ b/makima/src/daemon/task/manager.rs
@@ -117,6 +117,7 @@ fn get_auth_flow_storage() -> &'static std::sync::Mutex<Option<std::sync::mpsc::
}
/// Send an auth code to the pending OAuth flow.
+/// Deprecated: The new setup-token flow outputs tokens directly, so this is no longer needed.
pub fn send_auth_code(code: &str) -> bool {
let storage = get_auth_flow_storage();
if let Ok(mut guard) = storage.lock() {
@@ -127,16 +128,68 @@ pub fn send_auth_code(code: &str) -> bool {
}
}
}
- tracing::warn!("No pending auth flow to send code to");
+ tracing::warn!("No pending auth flow to send code to (this is expected with the new token-based flow)");
false
}
+/// Extract an OAuth token from a line of setup-token output.
+/// Looks for tokens matching the `sk-ant-oat01-` prefix format.
+fn extract_oauth_token(line: &str) -> Option<String> {
+ let trimmed = line.trim();
+ if trimmed.starts_with("sk-ant-oat01-") {
+ Some(trimmed.to_string())
+ } else {
+ None
+ }
+}
+
+/// Save an OAuth token to the ~/.makima directory for later use by spawned Claude processes.
+fn save_oauth_token(token: &str) -> std::io::Result<()> {
+ let makima_dir = dirs::home_dir()
+ .unwrap_or_default()
+ .join(".makima");
+ std::fs::create_dir_all(&makima_dir)?;
+ let token_path = makima_dir.join("claude_oauth_token");
+ std::fs::write(&token_path, token)?;
+ // Set restrictive permissions on Unix
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::PermissionsExt;
+ std::fs::set_permissions(&token_path, std::fs::Permissions::from_mode(0o600))?;
+ }
+ tracing::info!(path = %token_path.display(), "Saved OAuth token to disk");
+ Ok(())
+}
+
+/// Load a previously saved OAuth token from ~/.makima/claude_oauth_token.
+/// Returns None if no token file exists or is empty.
+pub fn load_oauth_token() -> Option<String> {
+ let token_path = dirs::home_dir()?
+ .join(".makima")
+ .join("claude_oauth_token");
+ std::fs::read_to_string(&token_path).ok()
+ .map(|s| s.trim().to_string())
+ .filter(|s| !s.is_empty())
+}
+
+/// Result of the OAuth login flow initiated by `get_oauth_login_url`.
+/// Contains the URL for the user to visit, plus a receiver for when the token is saved.
+struct OAuthFlowResult {
+ /// The OAuth login URL the user should visit.
+ login_url: String,
+ /// Receiver that will yield the saved token once authentication completes.
+ token_rx: tokio::sync::oneshot::Receiver<String>,
+}
+
/// Spawn `claude setup-token` to initiate OAuth flow and capture the login URL.
/// This spawns the process in a PTY (required by Ink) and reads output until we find a URL.
-/// The process continues running in the background waiting for auth completion.
-async fn get_oauth_login_url(claude_command: &str) -> Option<String> {
+///
+/// The new `claude setup-token` flow outputs a token directly (sk-ant-oat01-...) after
+/// the user completes browser authentication, so no code submission is needed.
+/// The token is automatically detected, saved to disk, and reported via the token_rx channel.
+async fn get_oauth_login_url(claude_command: &str) -> Option<OAuthFlowResult> {
use portable_pty::{native_pty_system, CommandBuilder, PtySize};
- use std::io::{Read, Write};
+ use std::io::Read;
tracing::info!("Spawning claude setup-token in PTY to get OAuth login URL");
@@ -173,7 +226,7 @@ async fn get_oauth_login_url(claude_command: &str) -> Option<String> {
}
};
- // Get the reader and writer from the master side
+ // Get the reader from the master side
let mut reader = match pair.master.try_clone_reader() {
Ok(reader) => reader,
Err(e) => {
@@ -182,7 +235,8 @@ async fn get_oauth_login_url(claude_command: &str) -> Option<String> {
}
};
- let mut writer = match pair.master.take_writer() {
+ // Take the writer - we keep it alive but don't need to write auth codes anymore
+ let _writer = match pair.master.take_writer() {
Ok(writer) => writer,
Err(e) => {
tracing::error!(error = %e, "Failed to take PTY writer");
@@ -191,22 +245,25 @@ async fn get_oauth_login_url(claude_command: &str) -> Option<String> {
};
// Create channels for communication
- let (code_tx, code_rx) = std::sync::mpsc::channel::<String>();
let (url_tx, url_rx) = std::sync::mpsc::channel::<String>();
+ let (token_tx, token_rx) = tokio::sync::oneshot::channel::<String>();
- // Store the code sender globally so it can be used when AUTH_CODE message arrives
+ // Also store a legacy code sender for backward compatibility (in case old server sends SubmitAuthCode)
{
+ let (code_tx, _code_rx) = std::sync::mpsc::channel::<String>();
let storage = get_auth_flow_storage();
if let Ok(mut guard) = storage.lock() {
*guard = Some(code_tx);
}
}
- // Spawn reader thread - reads PTY output and sends URL when found
+ // Spawn reader thread - reads PTY output, sends URL when found, and watches for token
let reader_handle = std::thread::spawn(move || {
let mut buffer = [0u8; 4096];
let mut accumulated = String::new();
let mut url_sent = false;
+ let mut token_saved = false;
+ let mut token_tx = Some(token_tx);
let mut read_count = 0;
tracing::info!("setup-token reader thread started");
@@ -241,6 +298,22 @@ async fn get_oauth_login_url(claude_command: &str) -> Option<String> {
}
}
+ // Look for OAuth token in output (new setup-token format)
+ if !token_saved {
+ if let Some(token) = extract_oauth_token(&clean_line) {
+ tracing::info!("Found OAuth token in setup-token output");
+ if let Err(e) = save_oauth_token(&token) {
+ tracing::error!(error = %e, "Failed to save OAuth token");
+ } else {
+ tracing::info!("OAuth token saved successfully");
+ }
+ if let Some(tx) = token_tx.take() {
+ let _ = tx.send(token);
+ }
+ token_saved = true;
+ }
+ }
+
// Check for success/failure messages
if clean_line.contains("successfully") || clean_line.contains("authenticated") || clean_line.contains("Success") {
tracing::info!("Authentication appears successful!");
@@ -256,39 +329,12 @@ async fn get_oauth_login_url(claude_command: &str) -> Option<String> {
}
}
}
- tracing::info!("setup-token reader thread ended");
+ tracing::info!("setup-token reader thread ended (token_saved={})", token_saved);
});
- // Spawn writer thread - waits for auth code and writes it to PTY
+ // Spawn cleanup thread - waits for reader to finish and cleans up the child process
std::thread::spawn(move || {
- tracing::info!("setup-token writer thread started, waiting for auth code (10 min timeout)");
-
- // Wait for auth code from frontend (with long timeout - user needs time to authenticate)
- match code_rx.recv_timeout(std::time::Duration::from_secs(600)) {
- Ok(code) => {
- tracing::info!(code_len = code.len(), "Received auth code from frontend, writing to PTY");
- // Write code followed by carriage return (Enter key in raw terminal mode)
- let code_with_enter = format!("{}\r", code);
- if let Err(e) = writer.write_all(code_with_enter.as_bytes()) {
- tracing::error!(error = %e, "Failed to write auth code to PTY");
- } else if let Err(e) = writer.flush() {
- tracing::error!(error = %e, "Failed to flush PTY writer");
- } else {
- tracing::info!("Auth code written to setup-token PTY successfully");
- // Give Ink a moment to process, then send another Enter in case first was buffered
- std::thread::sleep(std::time::Duration::from_millis(100));
- let _ = writer.write_all(b"\r");
- let _ = writer.flush();
- tracing::info!("Sent additional Enter keypress");
- }
- }
- Err(e) => {
- tracing::info!(error = %e, "Auth code receive ended (timeout or channel closed)");
- }
- }
-
- // Wait for reader thread to finish
- tracing::debug!("Waiting for reader thread to finish...");
+ tracing::debug!("setup-token cleanup thread: waiting for reader thread to finish...");
let _ = reader_handle.join();
// Wait for child to fully exit
@@ -301,11 +347,17 @@ async fn get_oauth_login_url(claude_command: &str) -> Option<String> {
tracing::error!(error = %e, "Failed to wait for setup-token process");
}
}
+
+ // Keep _writer alive until here so PTY stays open
+ drop(_writer);
});
// Wait for URL with timeout
match url_rx.recv_timeout(std::time::Duration::from_secs(30)) {
- Ok(url) => Some(url),
+ Ok(login_url) => Some(OAuthFlowResult {
+ login_url,
+ token_rx,
+ }),
Err(e) => {
tracing::error!(error = %e, "Timed out waiting for OAuth login URL");
None
@@ -1894,15 +1946,60 @@ impl TaskManager {
// Spawn in a task so it doesn't block command handling
tokio::spawn(async move {
match get_oauth_login_url(&claude_command).await {
- Some(login_url) => {
- tracing::info!(request_id = %request_id, login_url = %login_url, "Got OAuth login URL for reauth");
+ Some(flow_result) => {
+ tracing::info!(request_id = %request_id, login_url = %flow_result.login_url, "Got OAuth login URL for reauth");
+ // Send url_ready status immediately
let msg = DaemonMessage::ReauthStatus {
request_id,
status: "url_ready".to_string(),
- login_url: Some(login_url),
+ login_url: Some(flow_result.login_url),
error: None,
+ token_saved: false,
};
let _ = ws_tx.send(msg).await;
+
+ // Now wait for the token to be detected and saved (up to 10 minutes)
+ let ws_tx_token = ws_tx.clone();
+ tokio::spawn(async move {
+ match tokio::time::timeout(
+ std::time::Duration::from_secs(600),
+ flow_result.token_rx,
+ ).await {
+ Ok(Ok(_token)) => {
+ tracing::info!(request_id = %request_id, "OAuth token received and saved, reporting completion");
+ let msg = DaemonMessage::ReauthStatus {
+ request_id,
+ status: "completed".to_string(),
+ login_url: None,
+ error: None,
+ token_saved: true,
+ };
+ let _ = ws_tx_token.send(msg).await;
+ }
+ Ok(Err(_)) => {
+ tracing::warn!(request_id = %request_id, "Token channel closed without receiving token");
+ let msg = DaemonMessage::ReauthStatus {
+ request_id,
+ status: "failed".to_string(),
+ login_url: None,
+ error: Some("setup-token process ended without producing a token".to_string()),
+ token_saved: false,
+ };
+ let _ = ws_tx_token.send(msg).await;
+ }
+ Err(_) => {
+ tracing::warn!(request_id = %request_id, "Timed out waiting for OAuth token (10 min)");
+ let msg = DaemonMessage::ReauthStatus {
+ request_id,
+ status: "failed".to_string(),
+ login_url: None,
+ error: Some("Timed out waiting for authentication to complete".to_string()),
+ token_saved: false,
+ };
+ let _ = ws_tx_token.send(msg).await;
+ }
+ }
+ });
}
None => {
tracing::error!(request_id = %request_id, "Failed to get OAuth login URL for reauth");
@@ -1911,40 +2008,25 @@ impl TaskManager {
status: "failed".to_string(),
login_url: None,
error: Some("Failed to get OAuth login URL from setup-token".to_string()),
+ token_saved: false,
};
let _ = ws_tx.send(msg).await;
}
}
});
}
- DaemonCommand::SubmitAuthCode { request_id, code } => {
- tracing::info!(request_id = %request_id, "Received auth code submission from server");
- let ws_tx = self.ws_tx.clone();
-
- if send_auth_code(&code) {
- tracing::info!(request_id = %request_id, "Auth code forwarded to setup-token for reauth");
- // Wait a short time then report completion
- // (the setup-token process takes a moment to complete)
- tokio::spawn(async move {
- tokio::time::sleep(std::time::Duration::from_secs(3)).await;
- let msg = DaemonMessage::ReauthStatus {
- request_id,
- status: "completed".to_string(),
- login_url: None,
- error: None,
- };
- let _ = ws_tx.send(msg).await;
- });
- } else {
- tracing::warn!(request_id = %request_id, "No pending auth flow to receive code for reauth");
- let msg = DaemonMessage::ReauthStatus {
- request_id,
- status: "failed".to_string(),
- login_url: None,
- error: Some("No pending auth flow to receive the code. Try triggering reauth again.".to_string()),
- };
- let _ = self.ws_tx.send(msg).await;
- }
+ DaemonCommand::SubmitAuthCode { request_id, code: _ } => {
+ // Deprecated: The new setup-token flow outputs tokens directly.
+ // This handler is kept for backward compatibility but is a no-op.
+ tracing::info!(request_id = %request_id, "Received auth code submission (deprecated - new flow auto-detects token)");
+ let msg = DaemonMessage::ReauthStatus {
+ request_id,
+ status: "completed".to_string(),
+ login_url: None,
+ error: None,
+ token_saved: load_oauth_token().is_some(),
+ };
+ let _ = self.ws_tx.send(msg).await;
}
DaemonCommand::ApplyPatchToWorktree {
target_task_id,
diff --git a/makima/src/daemon/ws/protocol.rs b/makima/src/daemon/ws/protocol.rs
index bed5ffd..1611f52 100644
--- a/makima/src/daemon/ws/protocol.rs
+++ b/makima/src/daemon/ws/protocol.rs
@@ -133,13 +133,16 @@ pub enum DaemonMessage {
ReauthStatus {
#[serde(rename = "requestId")]
request_id: Uuid,
- /// Status: "url_ready", "completed", "failed"
+ /// Status: "pending", "url_ready", "completed", "failed"
status: String,
/// OAuth login URL (present when status is "url_ready")
#[serde(rename = "loginUrl")]
login_url: Option<String>,
/// Error message (present when status is "failed")
error: Option<String>,
+ /// Whether the OAuth token has been saved to disk
+ #[serde(rename = "tokenSaved", default)]
+ token_saved: bool,
},
// =========================================================================
diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs
index 6292e7b..32e55f0 100644
--- a/makima/src/db/models.rs
+++ b/makima/src/db/models.rs
@@ -2714,8 +2714,8 @@ pub struct Directive {
pub pr_url: Option<String>,
pub pr_branch: Option<String>,
pub completion_task_id: Option<Uuid>,
- /// Whether questions pause execution indefinitely until answered
- pub reconcile_mode: bool,
+ /// Question timeout mode: "auto" (30s timeout), "semi-auto" (block indefinitely), "manual" (block + ask many questions)
+ pub reconcile_mode: String,
pub goal_updated_at: DateTime<Utc>,
pub started_at: Option<DateTime<Utc>>,
pub version: i32,
@@ -2780,8 +2780,8 @@ pub struct DirectiveSummary {
pub orchestrator_task_id: Option<Uuid>,
pub pr_url: Option<String>,
pub completion_task_id: Option<Uuid>,
- /// Whether questions pause execution indefinitely until answered
- pub reconcile_mode: bool,
+ /// Question timeout mode: "auto" (30s timeout), "semi-auto" (block indefinitely), "manual" (block + ask many questions)
+ pub reconcile_mode: String,
pub version: i32,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
@@ -2808,8 +2808,8 @@ pub struct CreateDirectiveRequest {
pub repository_url: Option<String>,
pub local_path: Option<String>,
pub base_branch: Option<String>,
- /// Whether questions pause execution indefinitely until answered
- pub reconcile_mode: Option<bool>,
+ /// Question timeout mode: "auto", "semi-auto", or "manual"
+ pub reconcile_mode: Option<String>,
}
/// Request to update a directive.
@@ -2825,8 +2825,8 @@ pub struct UpdateDirectiveRequest {
pub orchestrator_task_id: Option<Uuid>,
pub pr_url: Option<String>,
pub pr_branch: Option<String>,
- /// Whether questions pause execution indefinitely until answered
- pub reconcile_mode: Option<bool>,
+ /// Question timeout mode: "auto", "semi-auto", or "manual"
+ pub reconcile_mode: Option<String>,
pub version: Option<i32>,
}
diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs
index 1af22f6..f14bc66 100644
--- a/makima/src/db/repository.rs
+++ b/makima/src/db/repository.rs
@@ -4941,7 +4941,7 @@ pub async fn create_directive_for_owner(
.bind(&req.repository_url)
.bind(&req.local_path)
.bind(&req.base_branch)
- .bind(req.reconcile_mode.unwrap_or(false))
+ .bind(req.reconcile_mode.as_deref().unwrap_or("auto"))
.fetch_one(pool)
.await
}
@@ -5059,7 +5059,7 @@ pub async fn update_directive_for_owner(
let orchestrator_task_id = req.orchestrator_task_id.or(current.orchestrator_task_id);
let pr_url = req.pr_url.as_deref().or(current.pr_url.as_deref());
let pr_branch = req.pr_branch.as_deref().or(current.pr_branch.as_deref());
- let reconcile_mode = req.reconcile_mode.unwrap_or(current.reconcile_mode);
+ let reconcile_mode = req.reconcile_mode.clone().unwrap_or_else(|| current.reconcile_mode.clone());
let result = sqlx::query_as::<_, Directive>(
r#"
@@ -5738,6 +5738,8 @@ pub struct StepForDispatch {
pub base_branch: Option<String>,
/// The directive's PR branch (if a PR has already been created from previous steps).
pub pr_branch: Option<String>,
+ /// The directive's reconcile mode: "auto", "semi-auto", or "manual".
+ pub reconcile_mode: String,
}
/// Get ready steps that need task dispatch.
@@ -5760,7 +5762,8 @@ pub async fn get_ready_steps_for_dispatch(
d.title AS directive_title,
d.repository_url,
d.base_branch,
- d.pr_branch
+ d.pr_branch,
+ d.reconcile_mode
FROM directive_steps ds
JOIN directives d ON d.id = ds.directive_id
WHERE ds.status = 'ready'
diff --git a/makima/src/orchestration/directive.rs b/makima/src/orchestration/directive.rs
index 155cfad..1e025c8 100644
--- a/makima/src/orchestration/directive.rs
+++ b/makima/src/orchestration/directive.rs
@@ -194,6 +194,14 @@ impl DirectiveOrchestrator {
String::new()
};
+ let manual_mode_appendix = if step.reconcile_mode == "manual" {
+ "\n\nIMPORTANT: This directive is in MANUAL reconcile mode. Before making assumptions or proceeding with implementation choices, you MUST ask clarification questions using:\n\
+ \x20 makima directive ask \"<question>\" --phaseguard\n\
+ Ask multiple targeted questions about requirements, edge cases, and design decisions. Wait for answers before writing code. Do not proceed until you have clear direction from the user."
+ } else {
+ ""
+ };
+
let plan = format!(
"You are executing a step in directive \"{directive_title}\".\n\n\
STEP: {step_name}\n\
@@ -203,12 +211,13 @@ impl DirectiveOrchestrator {
When done, the system will automatically mark this step as completed.\n\
If you cannot complete the task, report the failure clearly.\n\n\
If you need clarification or encounter a decision that requires user input, you can ask:\n\
- \x20 makima directive ask \"Your question\" --phaseguard",
+ \x20 makima directive ask \"Your question\" --phaseguard{manual_mode_appendix}",
directive_title = step.directive_title,
step_name = step.step_name,
description = step.step_description.as_deref().unwrap_or("(none)"),
merge_preamble = merge_preamble,
task_plan = task_plan,
+ manual_mode_appendix = manual_mode_appendix,
);
match self
@@ -1612,8 +1621,9 @@ If you need clarification from the user before finalizing the plan, you can ask
Use --phaseguard for questions that block progress (the question will wait indefinitely for a response).
The CLI automatically reconnects via polling every ~5 minutes to avoid HTTP timeout limits.
Without --phaseguard, questions timeout based on the directive's reconcile mode:
-- Reconcile ON: questions wait indefinitely (with automatic reconnecting polls every ~5 min)
-- Reconcile OFF: questions timeout after 30 seconds with no response
+- Auto: questions timeout after 30 seconds with no response
+- Semi-Auto: questions wait indefinitely (with automatic reconnecting polls every ~5 min)
+- Manual: questions wait indefinitely + tasks should ask multiple clarifying questions
When to ask:
- Requirements are ambiguous and multiple interpretations are valid
@@ -2206,8 +2216,9 @@ Options:
- `--phaseguard` - Block until response (recommended for important questions)
The question will appear in the directive UI. Behavior depends on reconcile mode:
-- Reconcile ON: blocks until user responds
-- Reconcile OFF: times out after 30s (use for non-critical questions)
+- Auto: times out after 30s (use for non-critical questions)
+- Semi-Auto: blocks until user responds
+- Manual: blocks until user responds (tasks are expected to ask many questions)
Use this when:
- The goal is ambiguous and could be interpreted multiple ways
diff --git a/makima/src/server/handlers/mesh_daemon.rs b/makima/src/server/handlers/mesh_daemon.rs
index 30439a4..d5ef1f9 100644
--- a/makima/src/server/handlers/mesh_daemon.rs
+++ b/makima/src/server/handlers/mesh_daemon.rs
@@ -354,13 +354,16 @@ pub enum DaemonMessage {
ReauthStatus {
#[serde(rename = "requestId")]
request_id: Uuid,
- /// Status: "url_ready", "completed", "failed"
+ /// Status: "pending", "url_ready", "completed", "failed"
status: String,
/// OAuth login URL (present when status is "url_ready")
#[serde(rename = "loginUrl")]
login_url: Option<String>,
/// Error message (present when status is "failed")
error: Option<String>,
+ /// Whether the OAuth token has been saved to disk
+ #[serde(rename = "tokenSaved", default)]
+ token_saved: bool,
},
/// Response to RetryCompletionAction command
CompletionActionResult {
@@ -1634,13 +1637,14 @@ async fn handle_daemon_connection(socket: WebSocket, state: SharedState, auth_re
"OAuth login URL available - user should open this in browser"
);
}
- Ok(DaemonMessage::ReauthStatus { request_id, status, login_url, error }) => {
+ Ok(DaemonMessage::ReauthStatus { request_id, status, login_url, error, token_saved }) => {
tracing::info!(
daemon_id = %daemon_uuid,
request_id = %request_id,
status = %status,
login_url = ?login_url,
error = ?error,
+ token_saved = token_saved,
"Daemon reauth status update"
);
diff --git a/makima/src/server/handlers/mesh_supervisor.rs b/makima/src/server/handlers/mesh_supervisor.rs
index 8c36500..9d2dce7 100644
--- a/makima/src/server/handlers/mesh_supervisor.rs
+++ b/makima/src/server/handlers/mesh_supervisor.rs
@@ -1715,21 +1715,21 @@ pub async fn ask_question(
let is_directive_context = directive_id.is_some() && contract_id.is_none();
// For directive context, check reconcile_mode to determine behavior
- let directive_reconcile_mode = if let Some(did) = directive_id {
+ let directive_reconcile_mode: String = if let Some(did) = directive_id {
if is_directive_context {
match repository::get_directive_for_owner(pool, owner_id, did).await {
- Ok(Some(d)) => d.reconcile_mode,
- Ok(None) => false,
+ Ok(Some(d)) => d.reconcile_mode.clone(),
+ Ok(None) => "auto".to_string(),
Err(e) => {
tracing::warn!(error = %e, "Failed to get directive for reconcile_mode check");
- false
+ "auto".to_string()
}
}
} else {
- false
+ "auto".to_string()
}
} else {
- false
+ "auto".to_string()
};
// Add the question (use Uuid::nil() for contract_id in directive-only context)
@@ -1813,7 +1813,7 @@ pub async fn ask_question(
}
// Determine if we should block indefinitely (phaseguard or directive reconcile mode)
- let use_phaseguard = request.phaseguard || (is_directive_context && directive_reconcile_mode);
+ let use_phaseguard = request.phaseguard || (is_directive_context && (directive_reconcile_mode == "semi-auto" || directive_reconcile_mode == "manual"));
// Poll for response with timeout
// - Phaseguard: block indefinitely until user responds
@@ -1823,7 +1823,7 @@ pub async fn ask_question(
// Cap at 5 minutes per HTTP request (well under Claude Code's 10-min limit).
// The CLI will automatically reconnect via the poll endpoint.
300
- } else if is_directive_context && !directive_reconcile_mode {
+ } else if is_directive_context && directive_reconcile_mode == "auto" {
30
} else {
request.timeout_seconds.max(1) as u64