summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-20 17:19:18 +0000
committerGitHub <noreply@github.com>2026-01-20 17:19:18 +0000
commit54c6d409e1d5667f4ab7f63a43e1459e68575c94 (patch)
tree13a4b58b00f67076b532933b9f8f208122d54bf9
parent5c79032637a9593f1530599726842f49ac904a13 (diff)
downloadsoryu-54c6d409e1d5667f4ab7f63a43e1459e68575c94.tar.gz
soryu-54c6d409e1d5667f4ab7f63a43e1459e68575c94.zip
Clean contract lifecycle: Add supervisor complete command (#13)
* feat: Add contract lifecycle management commands (complete and resume-contract) Add supervisor commands for properly managing contract lifecycle: - `makima supervisor complete` - Mark a contract as complete and stop the supervisor - `makima supervisor resume-contract` - Resume a completed contract (reactivate it) Also adds `api_key` field to TaskConfig for authenticated API calls. This consolidates the lifecycle management features from the contract-lifecycle-cleanup branch. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix: prevent supervisor auto-start for completed contracts When viewing a completed contract, the supervisor was auto-starting on component mount. This would resurrect completed contracts and cause them to continue running. Changes: - Add contract.status !== 'completed' check to the auto-start condition - Add contract.status to the useEffect dependency array Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Task completion checkpoint --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
-rw-r--r--makima/frontend/src/components/contracts/ContractCliInput.tsx6
-rw-r--r--makima/src/bin/makima.rs25
-rw-r--r--makima/src/daemon/api/supervisor.rs35
-rw-r--r--makima/src/daemon/cli/mod.rs6
-rw-r--r--makima/src/daemon/cli/supervisor.rs27
-rw-r--r--makima/src/daemon/task/manager.rs3
6 files changed, 99 insertions, 3 deletions
diff --git a/makima/frontend/src/components/contracts/ContractCliInput.tsx b/makima/frontend/src/components/contracts/ContractCliInput.tsx
index 821d03c..54d9f3a 100644
--- a/makima/frontend/src/components/contracts/ContractCliInput.tsx
+++ b/makima/frontend/src/components/contracts/ContractCliInput.tsx
@@ -279,9 +279,9 @@ export function ContractCliInput({ contractId, contract, onUpdate }: ContractCli
}
}, [messages]);
- // Auto-start supervisor when component mounts if it's pending
+ // Auto-start supervisor when component mounts if it's pending (but not for completed contracts)
useEffect(() => {
- if (supervisorTask && isSupervisorPending && !supervisorStarting) {
+ if (supervisorTask && isSupervisorPending && !supervisorStarting && contract.status !== 'completed') {
console.log("Auto-starting supervisor task on mount...");
ensureSupervisorStarted().then((started) => {
if (started) {
@@ -289,7 +289,7 @@ export function ContractCliInput({ contractId, contract, onUpdate }: ContractCli
}
});
}
- }, [supervisorTask?.id]); // Only run when task ID changes, not on every render
+ }, [supervisorTask?.id, contract.status]); // Only run when task ID or contract status changes
// Convert supervisor output events to messages
useEffect(() => {
diff --git a/makima/src/bin/makima.rs b/makima/src/bin/makima.rs
index 972e575..8b3e4dc 100644
--- a/makima/src/bin/makima.rs
+++ b/makima/src/bin/makima.rs
@@ -187,6 +187,7 @@ async fn run_daemon(
disable_verbose: config.process.disable_verbose,
bubblewrap: bubblewrap_config,
api_url,
+ api_key: config.server.api_key.clone(),
heartbeat_commit_interval_secs: config.process.heartbeat_commit_interval_secs,
};
@@ -439,6 +440,30 @@ async fn run_supervisor(
args.common.contract_id
);
}
+ SupervisorCommand::Complete(args) => {
+ let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
+ eprintln!("Marking contract {} as complete...", args.common.contract_id);
+ match client.supervisor_complete(args.common.contract_id).await {
+ Ok(_) => {
+ println!(r#"{{"success": true, "message": "Contract marked as complete"}}"#);
+ }
+ Err(e) => {
+ eprintln!("Error: {}", e);
+ println!(r#"{{"success": false, "error": "{}"}}"#, e);
+ std::process::exit(1);
+ }
+ }
+ }
+ SupervisorCommand::ResumeContract(args) => {
+ let client = ApiClient::new(args.api_url, args.api_key)?;
+ eprintln!("Resuming contract {}...", args.contract_id);
+ let result = client.supervisor_resume_contract(args.contract_id).await?;
+ println!("{}", serde_json::to_string(&serde_json::json!({
+ "success": true,
+ "message": "Contract resumed",
+ "contract": result.0
+ }))?);
+ }
}
Ok(())
diff --git a/makima/src/daemon/api/supervisor.rs b/makima/src/daemon/api/supervisor.rs
index 9614cfc..bd3aefd 100644
--- a/makima/src/daemon/api/supervisor.rs
+++ b/makima/src/daemon/api/supervisor.rs
@@ -249,6 +249,41 @@ impl ApiClient {
.await
}
+ /// Mark a contract as complete.
+ /// This will update contract status to 'completed', stop the supervisor, and clean up worktrees.
+ pub async fn supervisor_complete(&self, contract_id: Uuid) -> Result<JsonValue, ApiError> {
+ #[derive(Serialize)]
+ #[serde(rename_all = "camelCase")]
+ struct CompleteContractRequest {
+ status: String,
+ }
+ let req = CompleteContractRequest {
+ status: "completed".to_string(),
+ };
+ self.put(&format!("/api/v1/contracts/{}", contract_id), &req)
+ .await
+ }
+
+ /// Resume a completed contract (reactivate it).
+ ///
+ /// This updates the contract status from 'completed' back to 'active'
+ /// and optionally respawns the supervisor task.
+ pub async fn supervisor_resume_contract(
+ &self,
+ contract_id: Uuid,
+ ) -> Result<JsonValue, ApiError> {
+ #[derive(Serialize)]
+ #[serde(rename_all = "camelCase")]
+ struct ResumeContractRequest {
+ status: String,
+ }
+ let req = ResumeContractRequest {
+ status: "active".to_string(),
+ };
+ self.put(&format!("/api/v1/contracts/{}", contract_id), &req)
+ .await
+ }
+
/// Delete a task.
pub async fn delete_task(&self, task_id: Uuid) -> Result<(), ApiError> {
self.delete(&format!("/api/v1/mesh/tasks/{}", task_id)).await
diff --git a/makima/src/daemon/cli/mod.rs b/makima/src/daemon/cli/mod.rs
index ba71c28..3394b35 100644
--- a/makima/src/daemon/cli/mod.rs
+++ b/makima/src/daemon/cli/mod.rs
@@ -122,6 +122,12 @@ pub enum SupervisorCommand {
/// Rewind supervisor conversation
RewindConversation(supervisor::ConversationRewindArgs),
+
+ /// Mark the contract as complete and stop the supervisor
+ Complete(supervisor::CompleteArgs),
+
+ /// Resume a completed contract (reactivate it)
+ ResumeContract(supervisor::ResumeContractArgs),
}
/// Contract subcommands for task-contract interaction.
diff --git a/makima/src/daemon/cli/supervisor.rs b/makima/src/daemon/cli/supervisor.rs
index ae1a126..798a55f 100644
--- a/makima/src/daemon/cli/supervisor.rs
+++ b/makima/src/daemon/cli/supervisor.rs
@@ -390,3 +390,30 @@ pub struct ConversationRewindArgs {
#[arg(long)]
pub rewind_code: bool,
}
+
+/// Arguments for complete command (mark contract as complete).
+#[derive(Args, Debug)]
+pub struct CompleteArgs {
+ #[command(flatten)]
+ pub common: SupervisorArgs,
+}
+
+// ============================================================================
+// Resume Contract Command Args
+// ============================================================================
+
+/// Arguments for resume-contract command (reactivate a completed contract).
+#[derive(Args, Debug)]
+pub struct ResumeContractArgs {
+ /// API URL
+ #[arg(long, env = "MAKIMA_API_URL", default_value = "https://api.makima.jp")]
+ pub api_url: String,
+
+ /// API key for authentication
+ #[arg(long, env = "MAKIMA_API_KEY")]
+ pub api_key: String,
+
+ /// Contract ID to resume
+ #[arg(index = 1)]
+ pub contract_id: Uuid,
+}
diff --git a/makima/src/daemon/task/manager.rs b/makima/src/daemon/task/manager.rs
index 80b7039..555cd2a 100644
--- a/makima/src/daemon/task/manager.rs
+++ b/makima/src/daemon/task/manager.rs
@@ -980,6 +980,8 @@ pub struct TaskConfig {
pub bubblewrap: Option<crate::daemon::config::BubblewrapConfig>,
/// API URL for spawned tasks (HTTP endpoint for makima CLI).
pub api_url: String,
+ /// API key for making authenticated API calls.
+ pub api_key: String,
/// Interval in seconds between heartbeat commits (WIP checkpoints).
/// Set to 0 to disable. Default: 300 (5 minutes).
pub heartbeat_commit_interval_secs: u64,
@@ -998,6 +1000,7 @@ impl Default for TaskConfig {
disable_verbose: false,
bubblewrap: None,
api_url: "https://api.makima.jp".to_string(),
+ api_key: String::new(),
heartbeat_commit_interval_secs: 300, // 5 minutes
}
}