diff options
| author | soryu <soryu@soryu.co> | 2026-01-20 17:19:18 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-20 17:19:18 +0000 |
| commit | 54c6d409e1d5667f4ab7f63a43e1459e68575c94 (patch) | |
| tree | 13a4b58b00f67076b532933b9f8f208122d54bf9 | |
| parent | 5c79032637a9593f1530599726842f49ac904a13 (diff) | |
| download | soryu-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.tsx | 6 | ||||
| -rw-r--r-- | makima/src/bin/makima.rs | 25 | ||||
| -rw-r--r-- | makima/src/daemon/api/supervisor.rs | 35 | ||||
| -rw-r--r-- | makima/src/daemon/cli/mod.rs | 6 | ||||
| -rw-r--r-- | makima/src/daemon/cli/supervisor.rs | 27 | ||||
| -rw-r--r-- | makima/src/daemon/task/manager.rs | 3 |
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 } } |
