summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-29 17:22:59 +0000
committersoryu <soryu@soryu.co>2026-01-29 17:23:03 +0000
commit55bf0714a20e651ab70b1eed01ec665cfefac6b4 (patch)
treea25baeb7577f6affd85ae34718a175f3a659af88
parent4f1d67797dd56046665b772702b6b38fda9aa039 (diff)
downloadsoryu-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.tsx6
-rw-r--r--makima/frontend/tsconfig.tsbuildinfo2
-rw-r--r--makima/src/daemon/task/manager.rs90
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() {