- Add isProcessingWorkOrder flag to track Agent work order processing - Update onRequestWorkspaceMode with mode transition matrix validation - Idle → User/Agent: Always allowed - User → Agent: Always allowed (file editor checks for future) - Agent → User: Only if !isProcessingWorkOrder - All other transitions: Rejected with reason - Extend RequestWorkspaceModeCallback with optional reason parameter - Update frontend socket client to capture rejection reason - Update handleWorkspaceModeChange to display rejection reason in toast - Update WorkspaceModeIndicator to allow mode transitions per matrix - Fix FilesPanel RW/RO indicator swap bug - Document mode transition matrix and behavior in workspace-management.md
102 lines
2.9 KiB
TypeScript
102 lines
2.9 KiB
TypeScript
import { WorkspaceMode } from '../lib/types';
|
|
|
|
interface WorkspaceModeIndicatorProps {
|
|
mode: WorkspaceMode;
|
|
onChange?: (mode: WorkspaceMode) => void;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
const MODE_LABELS: Record<WorkspaceMode, string> = {
|
|
[WorkspaceMode.Idle]: 'I',
|
|
[WorkspaceMode.Syncing]: 'S',
|
|
[WorkspaceMode.User]: 'U',
|
|
[WorkspaceMode.Agent]: 'A',
|
|
};
|
|
|
|
const MODE_COLORS: Record<WorkspaceMode, string> = {
|
|
[WorkspaceMode.Idle]: 'text-gray-500 border-gray-500',
|
|
[WorkspaceMode.Syncing]: 'text-yellow-500 border-yellow-500',
|
|
[WorkspaceMode.User]: 'text-blue-400 border-blue-400',
|
|
[WorkspaceMode.Agent]: 'text-green-500 border-green-500',
|
|
};
|
|
|
|
export default function WorkspaceModeIndicator({
|
|
mode,
|
|
onChange,
|
|
disabled = false,
|
|
}: WorkspaceModeIndicatorProps) {
|
|
const modes = [
|
|
WorkspaceMode.Idle,
|
|
WorkspaceMode.Syncing,
|
|
WorkspaceMode.User,
|
|
WorkspaceMode.Agent,
|
|
];
|
|
|
|
const handleClick = (m: WorkspaceMode) => {
|
|
if (disabled || m === WorkspaceMode.Syncing) return;
|
|
if (m === mode) return;
|
|
|
|
switch (mode) {
|
|
case WorkspaceMode.Idle:
|
|
if (m === WorkspaceMode.User || m === WorkspaceMode.Agent) {
|
|
onChange?.(m);
|
|
}
|
|
break;
|
|
|
|
case WorkspaceMode.User:
|
|
if (m === WorkspaceMode.Agent) {
|
|
onChange?.(m);
|
|
}
|
|
break;
|
|
|
|
case WorkspaceMode.Agent:
|
|
if (m === WorkspaceMode.User) {
|
|
onChange?.(m);
|
|
}
|
|
break;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="flex items-center gap-1">
|
|
{modes.map((m) => {
|
|
const isActive = m === mode;
|
|
const isClickable =
|
|
!disabled && m !== WorkspaceMode.Syncing && m !== mode;
|
|
const colorClass = MODE_COLORS[m];
|
|
const inactiveClass = isActive
|
|
? `border-2 ${colorClass} strobe shadow-[inset_0_1px_3px_rgba(0,0,0,0.5)]`
|
|
: `border border-border-default`;
|
|
|
|
return (
|
|
<button
|
|
key={m}
|
|
onClick={() => handleClick(m)}
|
|
disabled={!isClickable}
|
|
title={
|
|
m === WorkspaceMode.Idle
|
|
? 'Idle'
|
|
: m === WorkspaceMode.Syncing
|
|
? 'Syncing'
|
|
: m === WorkspaceMode.User
|
|
? 'User Mode'
|
|
: 'Agent Mode'
|
|
}
|
|
className={`
|
|
w-7 h-7 flex items-center justify-center
|
|
font-mono font-bold text-sm rounded
|
|
transition-all duration-150 select-none
|
|
${inactiveClass}
|
|
${isActive ? `active:text-${colorClass.split('-')[1]}` : 'text-text-muted'}
|
|
${isClickable ? 'hover:border-border-highlight hover:text-text-secondary cursor-pointer' : ''}
|
|
${!isClickable && !isActive ? 'cursor-default' : ''}
|
|
${isActive ? 'translate-y-0.5 shadow-inner' : 'translate-y-0'}
|
|
`}
|
|
>
|
|
{MODE_LABELS[m]}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
} |