summaryrefslogtreecommitdiff
path: root/makima/frontend/src/components/contracts/CommandModePanel.tsx
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 /makima/frontend/src/components/contracts/CommandModePanel.tsx
parent4f1d67797dd56046665b772702b6b38fda9aa039 (diff)
downloadsoryu-55bf0714a20e651ab70b1eed01ec665cfefac6b4.tar.gz
soryu-55bf0714a20e651ab70b1eed01ec665cfefac6b4.zip
Rename to command mode and update worktree to show committed changes
Diffstat (limited to 'makima/frontend/src/components/contracts/CommandModePanel.tsx')
-rw-r--r--makima/frontend/src/components/contracts/CommandModePanel.tsx272
1 files changed, 272 insertions, 0 deletions
diff --git a/makima/frontend/src/components/contracts/CommandModePanel.tsx b/makima/frontend/src/components/contracts/CommandModePanel.tsx
new file mode 100644
index 0000000..832d5ec
--- /dev/null
+++ b/makima/frontend/src/components/contracts/CommandModePanel.tsx
@@ -0,0 +1,272 @@
+import { useState, useCallback } from "react";
+import { useNavigate } from "react-router";
+import type { ContractWithRelations } from "../../lib/api";
+import {
+ getSupervisorStatus,
+ startSupervisor,
+ stopSupervisor,
+ resumeSupervisor,
+ updateContract,
+ type SupervisorStatus,
+} from "../../lib/api";
+
+interface CommandModePanelProps {
+ contract: ContractWithRelations;
+ onUpdate: () => void;
+}
+
+const statusConfig: Record<
+ SupervisorStatus["status"],
+ { label: string; color: string; bgColor: string }
+> = {
+ not_configured: {
+ label: "Not Configured",
+ color: "text-[#555]",
+ bgColor: "bg-[#555]/10",
+ },
+ pending: {
+ label: "Ready",
+ color: "text-yellow-400",
+ bgColor: "bg-yellow-400/10",
+ },
+ starting: {
+ label: "Starting...",
+ color: "text-blue-400",
+ bgColor: "bg-blue-400/10",
+ },
+ running: {
+ label: "Running",
+ color: "text-green-400",
+ bgColor: "bg-green-400/10",
+ },
+ paused: {
+ label: "Paused",
+ color: "text-orange-400",
+ bgColor: "bg-orange-400/10",
+ },
+ done: {
+ label: "Completed",
+ color: "text-blue-400",
+ bgColor: "bg-blue-400/10",
+ },
+ failed: {
+ label: "Failed",
+ color: "text-red-400",
+ bgColor: "bg-red-400/10",
+ },
+};
+
+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 () => {
+ if (!supervisorStatus.supervisorTaskId) return;
+
+ setLoading(true);
+ setError(null);
+
+ try {
+ await startSupervisor(supervisorStatus.supervisorTaskId);
+ onUpdate();
+ } catch (e) {
+ setError(e instanceof Error ? e.message : "Failed to start command mode");
+ } finally {
+ setLoading(false);
+ }
+ }, [supervisorStatus.supervisorTaskId, onUpdate]);
+
+ const handleStop = useCallback(async () => {
+ if (!supervisorStatus.supervisorTaskId) return;
+
+ setLoading(true);
+ setError(null);
+
+ try {
+ await stopSupervisor(supervisorStatus.supervisorTaskId);
+ onUpdate();
+ } catch (e) {
+ setError(e instanceof Error ? e.message : "Failed to stop command mode");
+ } finally {
+ setLoading(false);
+ }
+ }, [supervisorStatus.supervisorTaskId, onUpdate]);
+
+ const handleResume = useCallback(async () => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ await resumeSupervisor(contract.id, { resumeMode: "continue" });
+ // After resuming, we need to start the task
+ if (supervisorStatus.supervisorTaskId) {
+ await startSupervisor(supervisorStatus.supervisorTaskId);
+ }
+ onUpdate();
+ } catch (e) {
+ setError(e instanceof Error ? e.message : "Failed to resume command mode");
+ } finally {
+ setLoading(false);
+ }
+ }, [contract.id, supervisorStatus.supervisorTaskId, onUpdate]);
+
+ const handlePhaseGuardChange = useCallback(async (enabled: boolean) => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ await updateContract(contract.id, {
+ phaseGuard: enabled,
+ version: contract.version,
+ });
+ onUpdate();
+ } catch (e) {
+ setError(e instanceof Error ? e.message : "Failed to update phase guard setting");
+ } finally {
+ setLoading(false);
+ }
+ }, [contract.id, contract.version, onUpdate]);
+
+ return (
+ <div className="space-y-3">
+ <div className="flex items-center justify-between">
+ <h3 className="font-mono text-xs text-[#75aafc] uppercase">
+ Command Mode
+ </h3>
+ <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 a Command Mode supervisor configured."
+ ) : supervisorStatus.status === "running" ? (
+ "Command Mode is actively working on this contract, spawning tasks and managing progress."
+ ) : supervisorStatus.status === "pending" ? (
+ "Command Mode is ready to start. Click 'Enable' to begin autonomous work."
+ ) : supervisorStatus.status === "paused" ? (
+ "Command Mode is paused. Click 'Resume' to continue work."
+ ) : supervisorStatus.status === "failed" ? (
+ "Command Mode encountered an error. You can resume to retry."
+ ) : supervisorStatus.status === "done" ? (
+ "Command Mode has completed its work on this contract."
+ ) : (
+ "Command Mode is initializing..."
+ )}
+ </p>
+
+ {error && (
+ <div className="px-3 py-2 bg-red-500/10 border border-red-400/30 font-mono text-xs text-red-400">
+ {error}
+ </div>
+ )}
+
+ <div className="flex gap-2">
+ {supervisorStatus.canStart && (
+ <button
+ onClick={handleStart}
+ 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 Command Mode"}
+ </button>
+ )}
+
+ {supervisorStatus.canResume && (
+ <button
+ onClick={handleResume}
+ 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 Command Mode"}
+ </button>
+ )}
+
+ {supervisorStatus.canStop && (
+ <button
+ onClick={handleStop}
+ 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 Command Mode"}
+ </button>
+ )}
+ </div>
+
+ {/* Phase Guard Toggle */}
+ <div className="pt-3 border-t border-dashed border-[rgba(117,170,252,0.2)]">
+ <label className="flex items-start gap-3 cursor-pointer group">
+ <div className="relative mt-0.5">
+ <input
+ type="checkbox"
+ checked={contract.phaseGuard ?? false}
+ onChange={(e) => handlePhaseGuardChange(e.target.checked)}
+ disabled={loading}
+ className="sr-only peer"
+ />
+ <div className="w-9 h-5 bg-[rgba(117,170,252,0.1)] border border-[rgba(117,170,252,0.3)] rounded-full peer-checked:bg-[rgba(117,170,252,0.3)] transition-colors peer-disabled:opacity-50" />
+ <div className="absolute left-0.5 top-0.5 w-4 h-4 bg-[#555] rounded-full transition-transform peer-checked:translate-x-4 peer-checked:bg-[#75aafc] peer-disabled:opacity-50" />
+ </div>
+ <div className="flex-1">
+ <div className="flex items-center gap-2">
+ <span className="font-mono text-sm text-[#dbe7ff] group-hover:text-white transition-colors">
+ Phase Guard
+ </span>
+ {contract.phaseGuard && (
+ <span className="px-1.5 py-0.5 text-[9px] font-mono uppercase bg-yellow-500/20 text-yellow-400 border border-yellow-400/30 rounded">
+ active
+ </span>
+ )}
+ </div>
+ <div className="font-mono text-xs text-[#555] mt-0.5">
+ Ask for confirmation before advancing to the next phase
+ </div>
+ </div>
+ </label>
+ </div>
+
+ {/* Show running indicator when active */}
+ {supervisorStatus.status === "running" && (
+ <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">
+ Command Mode is actively working
+ </span>
+ </div>
+ )}
+
+ {supervisorStatus.status === "starting" && (
+ <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 command mode...
+ </span>
+ </div>
+ )}
+ </div>
+ );
+}