diff options
| author | soryu <soryu@soryu.co> | 2026-01-29 17:22:59 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-01-29 17:23:03 +0000 |
| commit | 55bf0714a20e651ab70b1eed01ec665cfefac6b4 (patch) | |
| tree | a25baeb7577f6affd85ae34718a175f3a659af88 | |
| parent | 4f1d67797dd56046665b772702b6b38fda9aa039 (diff) | |
| download | soryu-55bf0714a20e651ab70b1eed01ec665cfefac6b4.tar.gz soryu-55bf0714a20e651ab70b1eed01ec665cfefac6b4.zip | |
Rename to command mode and update worktree to show committed changes
| -rw-r--r-- | makima/frontend/src/components/contracts/CommandModePanel.tsx (renamed from makima/frontend/src/components/contracts/AutopilotPanel.tsx) | 63 | ||||
| -rw-r--r-- | makima/frontend/src/components/contracts/ContractDetail.tsx | 6 | ||||
| -rw-r--r-- | makima/frontend/tsconfig.tsbuildinfo | 2 | ||||
| -rw-r--r-- | makima/src/daemon/task/manager.rs | 90 |
4 files changed, 123 insertions, 38 deletions
diff --git a/makima/frontend/src/components/contracts/AutopilotPanel.tsx b/makima/frontend/src/components/contracts/CommandModePanel.tsx index 1a13773..832d5ec 100644 --- a/makima/frontend/src/components/contracts/AutopilotPanel.tsx +++ b/makima/frontend/src/components/contracts/CommandModePanel.tsx @@ -1,4 +1,5 @@ import { useState, useCallback } from "react"; +import { useNavigate } from "react-router"; import type { ContractWithRelations } from "../../lib/api"; import { getSupervisorStatus, @@ -9,7 +10,7 @@ import { type SupervisorStatus, } from "../../lib/api"; -interface AutopilotPanelProps { +interface CommandModePanelProps { contract: ContractWithRelations; onUpdate: () => void; } @@ -55,11 +56,18 @@ const statusConfig: Record< }, }; -export function AutopilotPanel({ contract, onUpdate }: AutopilotPanelProps) { +export function CommandModePanel({ contract, onUpdate }: CommandModePanelProps) { + const navigate = useNavigate(); const [loading, setLoading] = useState(false); const [error, setError] = useState<string | null>(null); const supervisorStatus = getSupervisorStatus(contract); + + const handleGoToSupervisor = useCallback(() => { + if (supervisorStatus.supervisorTaskId) { + navigate(`/mesh/${supervisorStatus.supervisorTaskId}`); + } + }, [supervisorStatus.supervisorTaskId, navigate]); const config = statusConfig[supervisorStatus.status]; const handleStart = useCallback(async () => { @@ -72,7 +80,7 @@ export function AutopilotPanel({ contract, onUpdate }: AutopilotPanelProps) { await startSupervisor(supervisorStatus.supervisorTaskId); onUpdate(); } catch (e) { - setError(e instanceof Error ? e.message : "Failed to start autopilot"); + setError(e instanceof Error ? e.message : "Failed to start command mode"); } finally { setLoading(false); } @@ -88,7 +96,7 @@ export function AutopilotPanel({ contract, onUpdate }: AutopilotPanelProps) { await stopSupervisor(supervisorStatus.supervisorTaskId); onUpdate(); } catch (e) { - setError(e instanceof Error ? e.message : "Failed to stop autopilot"); + setError(e instanceof Error ? e.message : "Failed to stop command mode"); } finally { setLoading(false); } @@ -106,7 +114,7 @@ export function AutopilotPanel({ contract, onUpdate }: AutopilotPanelProps) { } onUpdate(); } catch (e) { - setError(e instanceof Error ? e.message : "Failed to resume autopilot"); + setError(e instanceof Error ? e.message : "Failed to resume command mode"); } finally { setLoading(false); } @@ -133,30 +141,41 @@ export function AutopilotPanel({ contract, onUpdate }: AutopilotPanelProps) { <div className="space-y-3"> <div className="flex items-center justify-between"> <h3 className="font-mono text-xs text-[#75aafc] uppercase"> - Autopilot Mode + Command Mode </h3> - <div - className={`px-2 py-1 rounded font-mono text-xs ${config.color} ${config.bgColor}`} - > - {config.label} + <div className="flex items-center gap-2"> + {supervisorStatus.supervisorTaskId && ( + <button + onClick={handleGoToSupervisor} + className="px-2 py-1 font-mono text-xs text-[#9bc3ff] hover:text-[#dbe7ff] hover:bg-[rgba(117,170,252,0.1)] transition-colors flex items-center gap-1" + > + <span className="text-[#75aafc]">▶</span> + Supervisor + </button> + )} + <div + className={`px-2 py-1 rounded font-mono text-xs ${config.color} ${config.bgColor}`} + > + {config.label} + </div> </div> </div> <p className="font-mono text-xs text-[#555]"> {supervisorStatus.status === "not_configured" ? ( - "This contract does not have an autopilot supervisor configured." + "This contract does not have a Command Mode supervisor configured." ) : supervisorStatus.status === "running" ? ( - "Autopilot is actively working on this contract, spawning tasks and managing progress." + "Command Mode is actively working on this contract, spawning tasks and managing progress." ) : supervisorStatus.status === "pending" ? ( - "Autopilot is ready to start. Click 'Enable' to begin autonomous work." + "Command Mode is ready to start. Click 'Enable' to begin autonomous work." ) : supervisorStatus.status === "paused" ? ( - "Autopilot is paused. Click 'Resume' to continue work." + "Command Mode is paused. Click 'Resume' to continue work." ) : supervisorStatus.status === "failed" ? ( - "Autopilot encountered an error. You can resume to retry." + "Command Mode encountered an error. You can resume to retry." ) : supervisorStatus.status === "done" ? ( - "Autopilot has completed its work on this contract." + "Command Mode has completed its work on this contract." ) : ( - "Autopilot is initializing..." + "Command Mode is initializing..." )} </p> @@ -173,7 +192,7 @@ export function AutopilotPanel({ contract, onUpdate }: AutopilotPanelProps) { disabled={loading} className="px-4 py-2 font-mono text-xs text-[#dbe7ff] bg-green-600/20 border border-green-400/50 hover:bg-green-600/30 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" > - {loading ? "Starting..." : "Enable Autopilot"} + {loading ? "Starting..." : "Enable Command Mode"} </button> )} @@ -183,7 +202,7 @@ export function AutopilotPanel({ contract, onUpdate }: AutopilotPanelProps) { disabled={loading} className="px-4 py-2 font-mono text-xs text-[#dbe7ff] bg-blue-600/20 border border-blue-400/50 hover:bg-blue-600/30 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" > - {loading ? "Resuming..." : "Resume Autopilot"} + {loading ? "Resuming..." : "Resume Command Mode"} </button> )} @@ -193,7 +212,7 @@ export function AutopilotPanel({ contract, onUpdate }: AutopilotPanelProps) { disabled={loading} className="px-4 py-2 font-mono text-xs text-[#dbe7ff] bg-orange-600/20 border border-orange-400/50 hover:bg-orange-600/30 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" > - {loading ? "Stopping..." : "Pause Autopilot"} + {loading ? "Stopping..." : "Pause Command Mode"} </button> )} </div> @@ -235,7 +254,7 @@ export function AutopilotPanel({ contract, onUpdate }: AutopilotPanelProps) { <div className="flex items-center gap-2 pt-2 border-t border-dashed border-[rgba(117,170,252,0.2)]"> <div className="w-2 h-2 rounded-full bg-green-400 animate-pulse" /> <span className="font-mono text-xs text-green-400"> - Autopilot is actively working + Command Mode is actively working </span> </div> )} @@ -244,7 +263,7 @@ export function AutopilotPanel({ contract, onUpdate }: AutopilotPanelProps) { <div className="flex items-center gap-2 pt-2 border-t border-dashed border-[rgba(117,170,252,0.2)]"> <div className="w-2 h-2 rounded-full bg-blue-400 animate-pulse" /> <span className="font-mono text-xs text-blue-400"> - Initializing autopilot... + Initializing command mode... </span> </div> )} diff --git a/makima/frontend/src/components/contracts/ContractDetail.tsx b/makima/frontend/src/components/contracts/ContractDetail.tsx index 6e31c84..e5e65f6 100644 --- a/makima/frontend/src/components/contracts/ContractDetail.tsx +++ b/makima/frontend/src/components/contracts/ContractDetail.tsx @@ -18,7 +18,7 @@ import { PhaseHint } from "./PhaseHint"; import { RepositoryPanel } from "./RepositoryPanel"; import { ContractCliInput } from "./ContractCliInput"; import { PhaseDeliverablesPanel } from "./PhaseDeliverablesPanel"; -import { AutopilotPanel } from "./AutopilotPanel"; +import { CommandModePanel } from "./CommandModePanel"; import { TaskTree } from "../mesh/TaskTree"; type Tab = "overview" | "repos" | "files" | "tasks"; @@ -294,8 +294,8 @@ function OverviewTab({ }) { return ( <div className="space-y-6"> - {/* Autopilot controls */} - <AutopilotPanel contract={contract} onUpdate={onRefresh} /> + {/* Command Mode controls */} + <CommandModePanel contract={contract} onUpdate={onRefresh} /> {/* Phase deliverables checklist */} <PhaseDeliverablesPanel diff --git a/makima/frontend/tsconfig.tsbuildinfo b/makima/frontend/tsconfig.tsbuildinfo index 804859b..3aa9cbf 100644 --- a/makima/frontend/tsconfig.tsbuildinfo +++ b/makima/frontend/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/autopilotpanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/gitactionspanel.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/patcheslistpanel.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/mesh/worktreefilespanel.tsx","./src/components/templates/templateeditor.tsx","./src/components/workflow/phasecolumn.tsx","./src/components/workflow/workflowboard.tsx","./src/components/workflow/workflowcontractcard.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usecontracts.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usespeakwebsocket.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/settings.tsx","./src/routes/speak.tsx","./src/routes/templates.tsx","./src/routes/workflow.tsx","./src/types/messages.ts","./src/types/templates.ts"],"version":"5.9.3"}
\ No newline at end of file +{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/commandmodepanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/gitactionspanel.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/patcheslistpanel.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/mesh/worktreefilespanel.tsx","./src/components/templates/templateeditor.tsx","./src/components/workflow/phasecolumn.tsx","./src/components/workflow/workflowboard.tsx","./src/components/workflow/workflowcontractcard.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usecontracts.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usespeakwebsocket.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/settings.tsx","./src/routes/speak.tsx","./src/routes/templates.tsx","./src/routes/workflow.tsx","./src/types/messages.ts","./src/types/templates.ts"],"version":"5.9.3"}
\ No newline at end of file diff --git a/makima/src/daemon/task/manager.rs b/makima/src/daemon/task/manager.rs index dd133a2..1e04ca1 100644 --- a/makima/src/daemon/task/manager.rs +++ b/makima/src/daemon/task/manager.rs @@ -3329,7 +3329,7 @@ impl TaskManager { &self, task_id: Uuid, ) -> Result<(), DaemonError> { - // Get task's worktree path and branch + // Get task's worktree path, branch, and base_branch // If the task shares a supervisor's worktree, use the supervisor's worktree info let task_info = { let tasks = self.tasks.read().await; @@ -3340,12 +3340,14 @@ impl TaskManager { tasks.get(&supervisor_task_id).map(|supervisor| ( supervisor.worktree.as_ref().map(|w| w.path.clone()), supervisor.worktree.as_ref().map(|w| w.branch.clone()), + supervisor.base_branch.clone(), )) } else { // Use the task's own worktree Some(( task.worktree.as_ref().map(|w| w.path.clone()), task.worktree.as_ref().map(|w| w.branch.clone()), + task.base_branch.clone(), )) } } else { @@ -3353,10 +3355,10 @@ impl TaskManager { } }; - let (worktree_path, branch) = match task_info { - Some((Some(path), branch)) => (Some(path), branch), - Some((None, _)) => (None, None), - None => (None, None), + let (worktree_path, branch, base_branch) = match task_info { + Some((Some(path), branch, base_branch)) => (Some(path), branch, base_branch), + Some((None, _, _)) => (None, None, None), + None => (None, None, None), }; if worktree_path.is_none() { @@ -3419,7 +3421,7 @@ impl TaskManager { .output() .await; - let status_lines: Vec<(String, String)> = match status_output { + let uncommitted_status_lines: Vec<(String, String)> = match status_output { Ok(output) if output.status.success() => { String::from_utf8_lossy(&output.stdout) .lines() @@ -3436,14 +3438,78 @@ impl TaskManager { _ => vec![], }; - // Get numstat for line counts (staged + unstaged) - let numstat_output = tokio::process::Command::new("git") - .current_dir(&path) - .args(["diff", "HEAD", "--numstat"]) - .output() - .await; + // If there are uncommitted changes, use them. Otherwise, compare against base branch. + // Track effective_base_branch for reuse in numstat query + let (status_lines, effective_base_for_diff) = if !uncommitted_status_lines.is_empty() { + (uncommitted_status_lines, None) + } else { + // No uncommitted changes - try to get committed changes vs base branch + // First, try to detect the base branch if not provided + let effective_base_branch = if let Some(ref base) = base_branch { + Some(base.clone()) + } else { + // Auto-detect the default branch + self.worktree_manager.detect_default_branch(&path).await.ok() + }; + + if let Some(ref base) = effective_base_branch { + // Get committed changes using git diff --name-status + let diff_base = format!("origin/{}...HEAD", base); + let name_status_output = tokio::process::Command::new("git") + .current_dir(&path) + .args(["diff", "--name-status", &diff_base]) + .output() + .await; + + let committed_status_lines: Vec<(String, String)> = match name_status_output { + Ok(output) if output.status.success() => { + String::from_utf8_lossy(&output.stdout) + .lines() + .filter_map(|line| { + let parts: Vec<&str> = line.splitn(2, '\t').collect(); + if parts.len() >= 2 { + let status = parts[0].trim().to_string(); + let file_path = parts[1].to_string(); + Some((file_path, status)) + } else { + None + } + }) + .collect() + } + _ => vec![], + }; + if !committed_status_lines.is_empty() { + (committed_status_lines, Some(base.clone())) + } else { + (vec![], None) + } + } else { + (vec![], None) + } + }; + + // Get numstat for line counts + // If we have effective_base_for_diff, compare against origin/{base_branch} + // Otherwise compare against HEAD for uncommitted changes let mut file_stats: std::collections::HashMap<String, (i32, i32)> = std::collections::HashMap::new(); + + let numstat_output = if let Some(ref base) = effective_base_for_diff { + let diff_base = format!("origin/{}...HEAD", base); + tokio::process::Command::new("git") + .current_dir(&path) + .args(["diff", "--numstat", &diff_base]) + .output() + .await + } else { + tokio::process::Command::new("git") + .current_dir(&path) + .args(["diff", "HEAD", "--numstat"]) + .output() + .await + }; + if let Ok(output) = numstat_output { if output.status.success() { for line in String::from_utf8_lossy(&output.stdout).lines() { |
