summaryrefslogtreecommitdiff
path: root/makima/src
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-16 12:23:49 +0000
committersoryu <soryu@soryu.co>2026-01-16 12:23:49 +0000
commit205ab8a223ddf6591a3e8bfc9108506502977c11 (patch)
treed768063acff233dbeea223d7b6ea69d7e3038300 /makima/src
parent05931d19bc0c161d0177c3f983d0cd903d5e8ae3 (diff)
downloadsoryu-205ab8a223ddf6591a3e8bfc9108506502977c11.tar.gz
soryu-205ab8a223ddf6591a3e8bfc9108506502977c11.zip
Fixup: use default api.makima.jp URL and fix default branch detection
Also add checkpointing/history
Diffstat (limited to 'makima/src')
-rw-r--r--makima/src/bin/makima.rs7
-rw-r--r--makima/src/daemon/api/client.rs8
-rw-r--r--makima/src/daemon/cli/contract.rs2
-rw-r--r--makima/src/daemon/cli/supervisor.rs2
-rw-r--r--makima/src/daemon/task/manager.rs18
-rw-r--r--makima/src/daemon/worktree/manager.rs61
-rw-r--r--makima/src/server/handlers/repository_history.rs19
-rw-r--r--makima/src/server/mod.rs8
8 files changed, 111 insertions, 14 deletions
diff --git a/makima/src/bin/makima.rs b/makima/src/bin/makima.rs
index 9c9ac77..47e627b 100644
--- a/makima/src/bin/makima.rs
+++ b/makima/src/bin/makima.rs
@@ -168,6 +168,12 @@ async fn run_daemon(
} else {
None
};
+ // Derive HTTP API URL from WebSocket server URL (wss://... -> https://...)
+ let api_url = config
+ .server
+ .url
+ .replace("wss://", "https://")
+ .replace("ws://", "http://");
let task_config = TaskConfig {
max_concurrent_tasks: config.process.max_concurrent_tasks,
worktree_base_dir: config.worktree.base_dir.clone(),
@@ -178,6 +184,7 @@ async fn run_daemon(
enable_permissions: config.process.enable_permissions,
disable_verbose: config.process.disable_verbose,
bubblewrap: bubblewrap_config,
+ api_url,
};
// Create task manager
diff --git a/makima/src/daemon/api/client.rs b/makima/src/daemon/api/client.rs
index b27d606..2318d5a 100644
--- a/makima/src/daemon/api/client.rs
+++ b/makima/src/daemon/api/client.rs
@@ -42,7 +42,9 @@ impl ApiClient {
let url = format!("{}{}", self.base_url, path);
let response = self.client
.get(&url)
+ // Send both headers - server will try tool key first, then API key
.header("X-Makima-Tool-Key", &self.api_key)
+ .header("X-Makima-API-Key", &self.api_key)
.send()
.await?;
@@ -58,7 +60,9 @@ impl ApiClient {
let url = format!("{}{}", self.base_url, path);
let response = self.client
.post(&url)
+ // Send both headers - server will try tool key first, then API key
.header("X-Makima-Tool-Key", &self.api_key)
+ .header("X-Makima-API-Key", &self.api_key)
.header("Content-Type", "application/json")
.json(body)
.send()
@@ -72,7 +76,9 @@ impl ApiClient {
let url = format!("{}{}", self.base_url, path);
let response = self.client
.post(&url)
+ // Send both headers - server will try tool key first, then API key
.header("X-Makima-Tool-Key", &self.api_key)
+ .header("X-Makima-API-Key", &self.api_key)
.send()
.await?;
@@ -88,7 +94,9 @@ impl ApiClient {
let url = format!("{}{}", self.base_url, path);
let response = self.client
.put(&url)
+ // Send both headers - server will try tool key first, then API key
.header("X-Makima-Tool-Key", &self.api_key)
+ .header("X-Makima-API-Key", &self.api_key)
.header("Content-Type", "application/json")
.json(body)
.send()
diff --git a/makima/src/daemon/cli/contract.rs b/makima/src/daemon/cli/contract.rs
index 5fef5ec..a443b85 100644
--- a/makima/src/daemon/cli/contract.rs
+++ b/makima/src/daemon/cli/contract.rs
@@ -7,7 +7,7 @@ use uuid::Uuid;
#[derive(Args, Debug, Clone)]
pub struct ContractArgs {
/// API URL
- #[arg(long, env = "MAKIMA_API_URL", default_value = "http://localhost:8080", global = true)]
+ #[arg(long, env = "MAKIMA_API_URL", default_value = "https://api.makima.jp", global = true)]
pub api_url: String,
/// API key for authentication
diff --git a/makima/src/daemon/cli/supervisor.rs b/makima/src/daemon/cli/supervisor.rs
index ba4fb2b..db30cf1 100644
--- a/makima/src/daemon/cli/supervisor.rs
+++ b/makima/src/daemon/cli/supervisor.rs
@@ -7,7 +7,7 @@ use uuid::Uuid;
#[derive(Args, Debug, Clone)]
pub struct SupervisorArgs {
/// API URL
- #[arg(long, env = "MAKIMA_API_URL", default_value = "http://localhost:8080")]
+ #[arg(long, env = "MAKIMA_API_URL", default_value = "https://api.makima.jp")]
pub api_url: String,
/// API key for authentication
diff --git a/makima/src/daemon/task/manager.rs b/makima/src/daemon/task/manager.rs
index 5491934..fccebc5 100644
--- a/makima/src/daemon/task/manager.rs
+++ b/makima/src/daemon/task/manager.rs
@@ -978,6 +978,8 @@ pub struct TaskConfig {
pub disable_verbose: bool,
/// Bubblewrap sandbox configuration.
pub bubblewrap: Option<crate::daemon::config::BubblewrapConfig>,
+ /// API URL for spawned tasks (HTTP endpoint for makima CLI).
+ pub api_url: String,
}
impl Default for TaskConfig {
@@ -992,6 +994,7 @@ impl Default for TaskConfig {
enable_permissions: false,
disable_verbose: false,
bubblewrap: None,
+ api_url: "https://api.makima.jp".to_string(),
}
}
}
@@ -1583,6 +1586,7 @@ impl TaskManager {
active_pids: self.active_pids.clone(),
git_user_email: self.git_user_email.clone(),
git_user_name: self.git_user_name.clone(),
+ api_url: self.config.api_url.clone(),
}
}
@@ -2877,6 +2881,7 @@ struct TaskManagerInner {
active_pids: Arc<RwLock<HashMap<Uuid, u32>>>,
git_user_email: Arc<RwLock<Option<String>>>,
git_user_name: Arc<RwLock<Option<String>>>,
+ api_url: String,
}
impl TaskManagerInner {
@@ -3196,8 +3201,7 @@ impl TaskManagerInner {
// Set up environment variables for makima CLI
let mut env = HashMap::new();
- // TODO: Make API URL configurable
- env.insert("MAKIMA_API_URL".to_string(), "http://localhost:8080".to_string());
+ env.insert("MAKIMA_API_URL".to_string(), self.api_url.clone());
env.insert("MAKIMA_API_KEY".to_string(), tool_key.clone());
env.insert("MAKIMA_TASK_ID".to_string(), task_id.to_string());
// Supervisor needs contract ID for its tools
@@ -3207,7 +3211,7 @@ impl TaskManagerInner {
tracing::info!(
task_id = %task_id,
- api_url = "http://localhost:8080",
+ api_url = %self.api_url,
tool_key_preview = &tool_key[..8.min(tool_key.len())],
"Set supervisor environment variables"
);
@@ -3252,14 +3256,13 @@ impl TaskManagerInner {
// Set up environment variables for makima CLI
let mut env = HashMap::new();
- // TODO: Make API URL configurable
- env.insert("MAKIMA_API_URL".to_string(), "http://localhost:8080".to_string());
+ env.insert("MAKIMA_API_URL".to_string(), self.api_url.clone());
env.insert("MAKIMA_API_KEY".to_string(), tool_key.clone());
env.insert("MAKIMA_TASK_ID".to_string(), task_id.to_string());
tracing::info!(
task_id = %task_id,
- api_url = "http://localhost:8080",
+ api_url = %self.api_url,
tool_key_preview = &tool_key[..8.min(tool_key.len())],
"Set orchestrator environment variables"
);
@@ -3313,7 +3316,7 @@ impl TaskManagerInner {
tracing::warn!(task_id = %task_id, "Failed to register contract tool key");
}
- env.insert("MAKIMA_API_URL".to_string(), "http://localhost:8080".to_string());
+ env.insert("MAKIMA_API_URL".to_string(), self.api_url.clone());
env.insert("MAKIMA_API_KEY".to_string(), tool_key);
env.insert("MAKIMA_TASK_ID".to_string(), task_id.to_string());
}
@@ -4126,6 +4129,7 @@ impl Clone for TaskManagerInner {
active_pids: self.active_pids.clone(),
git_user_email: self.git_user_email.clone(),
git_user_name: self.git_user_name.clone(),
+ api_url: self.api_url.clone(),
}
}
}
diff --git a/makima/src/daemon/worktree/manager.rs b/makima/src/daemon/worktree/manager.rs
index ff0e9e7..d370828 100644
--- a/makima/src/daemon/worktree/manager.rs
+++ b/makima/src/daemon/worktree/manager.rs
@@ -128,8 +128,30 @@ impl WorktreeManager {
/// Detect the default branch of a repository.
/// Tries to find HEAD's target, falling back to common branch names.
+ /// Works for both regular and bare repositories.
pub async fn detect_default_branch(&self, repo_path: &Path) -> Result<String, WorktreeError> {
- // Try to get the branch that HEAD points to
+ tracing::debug!("Detecting default branch for repo: {}", repo_path.display());
+
+ // First, try to read HEAD directly (works for bare repos)
+ // In bare repos, HEAD is a symbolic ref to the default branch
+ let output = Command::new("git")
+ .args(["symbolic-ref", "HEAD", "--short"])
+ .current_dir(repo_path)
+ .output()
+ .await?;
+
+ if output.status.success() {
+ let branch = String::from_utf8_lossy(&output.stdout).trim().to_string();
+ if !branch.is_empty() {
+ tracing::debug!("Detected default branch from HEAD: {}", branch);
+ return Ok(branch);
+ }
+ } else {
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ tracing::debug!("symbolic-ref HEAD failed: {}", stderr.trim());
+ }
+
+ // Try to get the branch that origin/HEAD points to (for regular clones)
let output = Command::new("git")
.args(["symbolic-ref", "refs/remotes/origin/HEAD", "--short"])
.current_dir(repo_path)
@@ -141,11 +163,12 @@ impl WorktreeManager {
// Remove "origin/" prefix if present
let branch = branch.strip_prefix("origin/").unwrap_or(&branch).to_string();
if !branch.is_empty() {
+ tracing::debug!("Detected default branch from origin/HEAD: {}", branch);
return Ok(branch);
}
}
- // Try common branch names
+ // Try common branch names in refs/heads (works for bare and regular repos)
for branch in ["main", "master", "develop", "trunk"] {
let output = Command::new("git")
.args(["rev-parse", "--verify", &format!("refs/heads/{}", branch)])
@@ -154,11 +177,12 @@ impl WorktreeManager {
.await?;
if output.status.success() {
+ tracing::debug!("Detected default branch from refs/heads: {}", branch);
return Ok(branch.to_string());
}
}
- // Fall back to getting the current branch
+ // Fall back to getting the current branch (for regular repos)
let output = Command::new("git")
.args(["rev-parse", "--abbrev-ref", "HEAD"])
.current_dir(repo_path)
@@ -168,12 +192,41 @@ impl WorktreeManager {
if output.status.success() {
let branch = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !branch.is_empty() && branch != "HEAD" {
+ tracing::debug!("Detected default branch from rev-parse: {}", branch);
+ return Ok(branch);
+ }
+ }
+
+ // Final fallback: list all branches and pick the first one
+ let output = Command::new("git")
+ .args(["for-each-ref", "--format=%(refname:short)", "refs/heads/", "--count=1"])
+ .current_dir(repo_path)
+ .output()
+ .await?;
+
+ if output.status.success() {
+ let branch = String::from_utf8_lossy(&output.stdout).trim().to_string();
+ if !branch.is_empty() {
+ tracing::warn!("Using first available branch as fallback: {}", branch);
return Ok(branch);
}
}
+ // Log what branches exist for debugging
+ let output = Command::new("git")
+ .args(["for-each-ref", "--format=%(refname)", "refs/"])
+ .current_dir(repo_path)
+ .output()
+ .await?;
+
+ let available_refs = String::from_utf8_lossy(&output.stdout);
+ tracing::error!(
+ "Could not detect default branch. Available refs:\n{}",
+ available_refs
+ );
+
Err(WorktreeError::GitCommand(
- "Could not detect default branch".to_string(),
+ format!("Could not detect default branch. Check if the repository at {} has any branches.", repo_path.display()),
))
}
diff --git a/makima/src/server/handlers/repository_history.rs b/makima/src/server/handlers/repository_history.rs
index c788d84..9c309c0 100644
--- a/makima/src/server/handlers/repository_history.rs
+++ b/makima/src/server/handlers/repository_history.rs
@@ -97,6 +97,14 @@ pub async fn get_repository_suggestions(
let limit = params.limit.unwrap_or(10).min(50); // Cap at 50 for safety
+ tracing::debug!(
+ owner_id = %auth.owner_id,
+ source_type = ?params.source_type,
+ query = ?params.query,
+ limit = limit,
+ "Fetching repository suggestions"
+ );
+
match repository::get_repository_suggestions(
pool,
auth.owner_id,
@@ -107,6 +115,17 @@ pub async fn get_repository_suggestions(
.await
{
Ok(entries) => {
+ // Debug log to help diagnose filtering issues
+ for entry in &entries {
+ tracing::debug!(
+ id = %entry.id,
+ name = %entry.name,
+ source_type = %entry.source_type,
+ has_url = entry.repository_url.is_some(),
+ has_path = entry.local_path.is_some(),
+ "Repository suggestion entry"
+ );
+ }
let total = entries.len() as i64;
Json(RepositoryHistoryListResponse { entries, total }).into_response()
}
diff --git a/makima/src/server/mod.rs b/makima/src/server/mod.rs
index e244a08..a4cb3d1 100644
--- a/makima/src/server/mod.rs
+++ b/makima/src/server/mod.rs
@@ -18,7 +18,7 @@ use tower_http::trace::TraceLayer;
use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi;
-use crate::server::handlers::{api_keys, chat, contract_chat, contract_daemon, contracts, file_ws, files, listen, mesh, mesh_chat, mesh_daemon, mesh_merge, mesh_supervisor, mesh_ws, repository_history, templates, transcript_analysis, users, versions};
+use crate::server::handlers::{api_keys, chat, contract_chat, contract_daemon, contracts, file_ws, files, history, listen, mesh, mesh_chat, mesh_daemon, mesh_merge, mesh_supervisor, mesh_ws, repository_history, templates, transcript_analysis, users, versions};
use crate::server::openapi::ApiDoc;
use crate::server::state::SharedState;
@@ -107,6 +107,7 @@ pub fn make_router(state: SharedState) -> Router {
// Checkpoint endpoints
.route("/mesh/tasks/{id}/checkpoint", post(mesh_supervisor::create_checkpoint))
.route("/mesh/tasks/{id}/checkpoints", get(mesh_supervisor::list_checkpoints))
+ .route("/mesh/tasks/{id}/conversation", get(history::get_task_conversation))
// Resume and rewind endpoints
.route("/mesh/tasks/{id}/rewind", post(mesh::rewind_task))
.route("/mesh/tasks/{id}/fork", post(mesh::fork_task))
@@ -166,6 +167,9 @@ pub fn make_router(state: SharedState) -> Router {
// Contract supervisor resume endpoints
.route("/contracts/{id}/supervisor/resume", post(mesh_supervisor::resume_supervisor))
.route("/contracts/{id}/supervisor/conversation/rewind", post(mesh_supervisor::rewind_conversation))
+ // History endpoints
+ .route("/contracts/{id}/history", get(history::get_contract_history))
+ .route("/contracts/{id}/supervisor/conversation", get(history::get_supervisor_conversation))
// Contract daemon endpoints (for tasks to interact with contracts)
.route("/contracts/{id}/daemon/status", get(contract_daemon::get_contract_status))
.route("/contracts/{id}/daemon/checklist", get(contract_daemon::get_contract_checklist))
@@ -198,6 +202,8 @@ pub fn make_router(state: SharedState) -> Router {
"/contracts/{id}/tasks/{task_id}",
post(contracts::add_task_to_contract).delete(contracts::remove_task_from_contract),
)
+ // Timeline endpoint (unified history for user)
+ .route("/timeline", get(history::get_timeline))
// Template endpoints
.route("/templates", get(templates::list_templates))
.route("/templates/{id}", get(templates::get_template))