mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-16 19:00:38 +09:00
feat(routines): add workspace-aware routine runs
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
36376968af
commit
909e8cd4c8
38 changed files with 15468 additions and 250 deletions
|
|
@ -26,7 +26,12 @@ function issueModeForExistingWorkspace(mode: string | null | undefined) {
|
|||
return "shared_workspace";
|
||||
}
|
||||
|
||||
function shouldPresentExistingWorkspaceSelection(issue: Issue) {
|
||||
function shouldPresentExistingWorkspaceSelection(issue: {
|
||||
executionWorkspaceId: string | null;
|
||||
executionWorkspacePreference: string | null;
|
||||
executionWorkspaceSettings: Issue["executionWorkspaceSettings"];
|
||||
currentExecutionWorkspace?: ExecutionWorkspace | null;
|
||||
}) {
|
||||
const persistedMode =
|
||||
issue.currentExecutionWorkspace?.mode
|
||||
?? issue.executionWorkspaceSettings?.mode
|
||||
|
|
@ -156,19 +161,44 @@ function statusBadge(status: string) {
|
|||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
interface IssueWorkspaceCardProps {
|
||||
issue: Issue;
|
||||
issue: Omit<
|
||||
Pick<
|
||||
Issue,
|
||||
| "companyId"
|
||||
| "projectId"
|
||||
| "projectWorkspaceId"
|
||||
| "executionWorkspaceId"
|
||||
| "executionWorkspacePreference"
|
||||
| "executionWorkspaceSettings"
|
||||
>,
|
||||
"companyId"
|
||||
> & {
|
||||
companyId: string | null;
|
||||
currentExecutionWorkspace?: ExecutionWorkspace | null;
|
||||
};
|
||||
project: { id: string; executionWorkspacePolicy?: { enabled?: boolean; defaultMode?: string | null; defaultProjectWorkspaceId?: string | null } | null; workspaces?: Array<{ id: string; isPrimary: boolean }> } | null;
|
||||
onUpdate: (data: Record<string, unknown>) => void;
|
||||
initialEditing?: boolean;
|
||||
livePreview?: boolean;
|
||||
onDraftChange?: (data: Record<string, unknown>, meta: { canSave: boolean }) => void;
|
||||
}
|
||||
|
||||
export function IssueWorkspaceCard({ issue, project, onUpdate }: IssueWorkspaceCardProps) {
|
||||
export function IssueWorkspaceCard({
|
||||
issue,
|
||||
project,
|
||||
onUpdate,
|
||||
initialEditing = false,
|
||||
livePreview = false,
|
||||
onDraftChange,
|
||||
}: IssueWorkspaceCardProps) {
|
||||
const { selectedCompanyId } = useCompany();
|
||||
const companyId = issue.companyId ?? selectedCompanyId;
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [editing, setEditing] = useState(initialEditing);
|
||||
|
||||
const { data: experimentalSettings } = useQuery({
|
||||
queryKey: queryKeys.instance.experimentalSettings,
|
||||
queryFn: () => instanceSettingsApi.getExperimental(),
|
||||
retry: false,
|
||||
});
|
||||
|
||||
const policyEnabled = experimentalSettings?.enableIsolatedWorkspaces === true
|
||||
|
|
@ -209,13 +239,16 @@ export function IssueWorkspaceCard({ issue, project, onUpdate }: IssueWorkspaceC
|
|||
?? workspace
|
||||
?? null;
|
||||
|
||||
const currentSelection = shouldPresentExistingWorkspaceSelection(issue)
|
||||
const configuredSelection = shouldPresentExistingWorkspaceSelection(issue)
|
||||
? "reuse_existing"
|
||||
: (
|
||||
issue.executionWorkspacePreference
|
||||
?? issue.executionWorkspaceSettings?.mode
|
||||
?? defaultExecutionWorkspaceModeForProject(project)
|
||||
);
|
||||
const currentSelection = configuredSelection === "operator_branch" || configuredSelection === "agent_default"
|
||||
? "shared_workspace"
|
||||
: configuredSelection;
|
||||
|
||||
const [draftSelection, setDraftSelection] = useState(currentSelection);
|
||||
const [draftExecutionWorkspaceId, setDraftExecutionWorkspaceId] = useState(issue.executionWorkspaceId ?? "");
|
||||
|
|
@ -245,24 +278,33 @@ export function IssueWorkspaceCard({ issue, project, onUpdate }: IssueWorkspaceC
|
|||
|
||||
const canSaveWorkspaceConfig = draftSelection !== "reuse_existing" || draftExecutionWorkspaceId.length > 0;
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
if (!canSaveWorkspaceConfig) return;
|
||||
onUpdate({
|
||||
executionWorkspacePreference: draftSelection,
|
||||
executionWorkspaceId: draftSelection === "reuse_existing" ? draftExecutionWorkspaceId || null : null,
|
||||
executionWorkspaceSettings: {
|
||||
mode:
|
||||
draftSelection === "reuse_existing"
|
||||
? issueModeForExistingWorkspace(configuredReusableWorkspace?.mode)
|
||||
: draftSelection,
|
||||
},
|
||||
});
|
||||
setEditing(false);
|
||||
}, [
|
||||
canSaveWorkspaceConfig,
|
||||
const buildWorkspaceDraftUpdate = useCallback(() => ({
|
||||
executionWorkspacePreference: draftSelection,
|
||||
executionWorkspaceId: draftSelection === "reuse_existing" ? draftExecutionWorkspaceId || null : null,
|
||||
executionWorkspaceSettings: {
|
||||
mode:
|
||||
draftSelection === "reuse_existing"
|
||||
? issueModeForExistingWorkspace(configuredReusableWorkspace?.mode)
|
||||
: draftSelection,
|
||||
},
|
||||
}), [
|
||||
configuredReusableWorkspace?.mode,
|
||||
draftExecutionWorkspaceId,
|
||||
draftSelection,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!onDraftChange) return;
|
||||
onDraftChange(buildWorkspaceDraftUpdate(), { canSave: canSaveWorkspaceConfig });
|
||||
}, [buildWorkspaceDraftUpdate, canSaveWorkspaceConfig, onDraftChange]);
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
if (!canSaveWorkspaceConfig) return;
|
||||
onUpdate(buildWorkspaceDraftUpdate());
|
||||
setEditing(false);
|
||||
}, [
|
||||
buildWorkspaceDraftUpdate,
|
||||
canSaveWorkspaceConfig,
|
||||
onUpdate,
|
||||
]);
|
||||
|
||||
|
|
@ -274,6 +316,8 @@ export function IssueWorkspaceCard({ issue, project, onUpdate }: IssueWorkspaceC
|
|||
|
||||
if (!policyEnabled || !project) return null;
|
||||
|
||||
const showEditingControls = livePreview || editing;
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-border p-3 space-y-2">
|
||||
{/* Header row */}
|
||||
|
|
@ -286,7 +330,7 @@ export function IssueWorkspaceCard({ issue, project, onUpdate }: IssueWorkspaceC
|
|||
{workspace ? statusBadge(workspace.status) : statusBadge("idle")}
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{editing ? (
|
||||
{!livePreview && editing ? (
|
||||
<>
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
@ -305,7 +349,7 @@ export function IssueWorkspaceCard({ issue, project, onUpdate }: IssueWorkspaceC
|
|||
Save
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
) : !livePreview ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
|
|
@ -314,12 +358,12 @@ export function IssueWorkspaceCard({ issue, project, onUpdate }: IssueWorkspaceC
|
|||
>
|
||||
<Pencil className="h-3 w-3 mr-1" />Edit
|
||||
</Button>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Read-only info */}
|
||||
{!editing && (
|
||||
{!showEditingControls && (
|
||||
<div className="space-y-1.5 text-xs">
|
||||
{workspace?.branchName && (
|
||||
<div className="flex items-center gap-1.5">
|
||||
|
|
@ -377,7 +421,7 @@ export function IssueWorkspaceCard({ issue, project, onUpdate }: IssueWorkspaceC
|
|||
)}
|
||||
|
||||
{/* Editing controls */}
|
||||
{editing && (
|
||||
{showEditingControls && (
|
||||
<div className="space-y-2 pt-1">
|
||||
<select
|
||||
className="w-full rounded border border-border bg-transparent px-2 py-1.5 text-xs outline-none"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue