Skip to content

Commit acb8df8

Browse files
authored
Merge branch 'main' into feat/admin-org-activity-endpoint
2 parents 59fc10b + 7be96c0 commit acb8df8

6 files changed

Lines changed: 149 additions & 16 deletions

File tree

apps/api/src/browserbase/browserbase.service.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
1515
const BROWSER_WIDTH = 1440;
1616
const BROWSER_HEIGHT = 900;
1717

18+
/** Stagehand v3 requires 'provider/model' format. */
19+
const STAGEHAND_MODEL = 'anthropic/claude-sonnet-4-6';
20+
const STAGEHAND_CUA_MODEL = 'anthropic/claude-sonnet-4-6';
21+
1822
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
1923

2024
const PENDING_CONTEXT_ID = '__PENDING__';
@@ -205,7 +209,7 @@ export class BrowserbaseService {
205209
projectId: this.getProjectId(),
206210
browserbaseSessionID: sessionId,
207211
model: {
208-
modelName: 'claude-3-7-sonnet-latest',
212+
modelName: STAGEHAND_MODEL,
209213
apiKey: process.env.ANTHROPIC_API_KEY,
210214
},
211215
verbose: 1,
@@ -784,7 +788,7 @@ export class BrowserbaseService {
784788
.agent({
785789
cua: true,
786790
model: {
787-
modelName: 'anthropic/claude-3-7-sonnet-latest',
791+
modelName: STAGEHAND_CUA_MODEL,
788792
apiKey: process.env.ANTHROPIC_API_KEY,
789793
},
790794
})

apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/BrowserAutomations.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ export function BrowserAutomations({ taskId, isManualTask = false }: BrowserAuto
130130
onRun={execution.runAutomation}
131131
onCreateClick={isManualTask ? undefined : () => setDialogState({ open: true, mode: 'create' })}
132132
onEditClick={(automation) => setDialogState({ open: true, mode: 'edit', automation })}
133+
onDelete={automations.deleteAutomation}
134+
onToggleEnabled={automations.toggleAutomation}
133135
/>
134136
<BrowserAutomationConfigDialog
135137
isOpen={dialogState.open}

apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/AutomationItem.tsx

Lines changed: 98 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,17 @@
22

33
import { cn } from '@/lib/utils';
44
import { Button } from '@trycompai/ui/button';
5-
import { ChevronDown, Loader2, MonitorPlay, Settings } from 'lucide-react';
5+
import {
6+
ChevronDown,
7+
Loader2,
8+
MonitorPlay,
9+
Pencil,
10+
Power,
11+
PowerOff,
12+
Trash2,
13+
} from 'lucide-react';
614
import { formatDistanceToNow } from 'date-fns';
15+
import { useState } from 'react';
716
import type { BrowserAutomation, BrowserAutomationRun } from '../../hooks/types';
817
import { RunHistory } from './RunHistory';
918

@@ -15,6 +24,8 @@ interface AutomationItemProps {
1524
onToggleExpand: () => void;
1625
onRun: () => void;
1726
onEdit: () => void;
27+
onDelete: () => void;
28+
onToggleEnabled: (enabled: boolean) => void;
1829
}
1930

2031
export function AutomationItem({
@@ -25,23 +36,30 @@ export function AutomationItem({
2536
onToggleExpand,
2637
onRun,
2738
onEdit,
39+
onDelete,
40+
onToggleEnabled,
2841
}: AutomationItemProps) {
42+
const [confirmDelete, setConfirmDelete] = useState(false);
2943
const runs: BrowserAutomationRun[] = automation.runs || [];
3044
const latestRun = runs[0];
3145

3246
// status dot
3347
const hasFailed = latestRun?.status === 'failed';
3448
const isCompleted = latestRun?.status === 'completed';
35-
const dotColor = hasFailed
36-
? 'bg-destructive shadow-[0_0_8px_rgba(255,0,0,0.3)]'
37-
: isCompleted
38-
? 'bg-primary shadow-[0_0_8px_rgba(0,77,64,0.4)]'
39-
: 'bg-muted-foreground';
49+
const isDisabled = !automation.isEnabled;
50+
const dotColor = isDisabled
51+
? 'bg-muted-foreground/40'
52+
: hasFailed
53+
? 'bg-destructive shadow-[0_0_8px_rgba(255,0,0,0.3)]'
54+
: isCompleted
55+
? 'bg-primary shadow-[0_0_8px_rgba(0,77,64,0.4)]'
56+
: 'bg-muted-foreground';
4057

4158
return (
4259
<div
4360
className={cn(
4461
'rounded-lg border transition-all duration-300',
62+
isDisabled && 'opacity-60',
4563
isExpanded
4664
? 'border-primary/30 shadow-sm bg-primary/2'
4765
: 'border-border/50 hover:border-border hover:shadow-sm',
@@ -51,9 +69,16 @@ export function AutomationItem({
5169
<div className={cn('h-2.5 w-2.5 rounded-full shrink-0', dotColor)} />
5270

5371
<div className="flex-1 min-w-0">
54-
<p className="font-semibold text-foreground text-sm tracking-tight">
55-
{automation.name}
56-
</p>
72+
<div className="flex items-center gap-2">
73+
<p className="font-semibold text-foreground text-sm tracking-tight">
74+
{automation.name}
75+
</p>
76+
{isDisabled && (
77+
<span className="text-[10px] text-muted-foreground bg-muted px-1.5 py-0.5 rounded">
78+
Paused
79+
</span>
80+
)}
81+
</div>
5782
{latestRun ? (
5883
<p className="text-xs text-muted-foreground mt-0.5">
5984
Last ran {formatDistanceToNow(new Date(latestRun.createdAt), { addSuffix: true })}
@@ -63,15 +88,75 @@ export function AutomationItem({
6388
)}
6489
</div>
6590

66-
<div className="flex items-center gap-2">
91+
<div className="flex items-center gap-1.5">
6792
{!readOnly && (
68-
<Button variant="ghost" size="icon" onClick={onEdit} aria-label="Edit automation">
69-
<Settings className="h-4 w-4" />
93+
<Button
94+
variant="ghost"
95+
size="icon"
96+
className="h-8 w-8"
97+
onClick={() => onToggleEnabled(!automation.isEnabled)}
98+
aria-label={automation.isEnabled ? 'Pause automation' : 'Enable automation'}
99+
>
100+
{automation.isEnabled ? (
101+
<Power className="h-3.5 w-3.5 text-primary" />
102+
) : (
103+
<PowerOff className="h-3.5 w-3.5 text-muted-foreground" />
104+
)}
70105
</Button>
71106
)}
72107

73108
{!readOnly && (
74-
<Button variant="outline" size="sm" onClick={onRun} disabled={isRunning}>
109+
<Button
110+
variant="ghost"
111+
size="icon"
112+
className="h-8 w-8"
113+
onClick={onEdit}
114+
aria-label="Edit automation"
115+
>
116+
<Pencil className="h-3.5 w-3.5" />
117+
</Button>
118+
)}
119+
120+
{!readOnly && (
121+
confirmDelete ? (
122+
<div className="flex items-center gap-1">
123+
<Button
124+
variant="destructive"
125+
size="sm"
126+
className="h-7 text-xs"
127+
onClick={() => { onDelete(); setConfirmDelete(false); }}
128+
>
129+
Confirm
130+
</Button>
131+
<Button
132+
variant="ghost"
133+
size="sm"
134+
className="h-7 text-xs"
135+
onClick={() => setConfirmDelete(false)}
136+
>
137+
Cancel
138+
</Button>
139+
</div>
140+
) : (
141+
<Button
142+
variant="ghost"
143+
size="icon"
144+
className="h-8 w-8"
145+
onClick={() => setConfirmDelete(true)}
146+
aria-label="Delete automation"
147+
>
148+
<Trash2 className="h-3.5 w-3.5 text-muted-foreground hover:text-destructive" />
149+
</Button>
150+
)
151+
)}
152+
153+
{!readOnly && (
154+
<Button
155+
variant="outline"
156+
size="sm"
157+
onClick={onRun}
158+
disabled={isRunning || isDisabled}
159+
>
75160
{isRunning ? (
76161
<>
77162
<Loader2 className="mr-1.5 h-3 w-3 animate-spin" />

apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/BrowserAutomationsList.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ const defaultProps = {
5555
onRun: vi.fn(),
5656
onCreateClick: vi.fn(),
5757
onEditClick: vi.fn(),
58+
onDelete: vi.fn(),
59+
onToggleEnabled: vi.fn(),
5860
};
5961

6062
describe('BrowserAutomationsList permission gating', () => {

apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/BrowserAutomationsList.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ interface BrowserAutomationsListProps {
3333
/** When undefined, the create button is hidden (e.g., for manual tasks) */
3434
onCreateClick?: () => void;
3535
onEditClick: (automation: BrowserAutomation) => void;
36+
onDelete: (automationId: string) => void;
37+
onToggleEnabled: (automationId: string, enabled: boolean) => void;
3638
}
3739

3840
export function BrowserAutomationsList({
@@ -42,13 +44,16 @@ export function BrowserAutomationsList({
4244
onRun,
4345
onCreateClick,
4446
onEditClick,
47+
onDelete,
48+
onToggleEnabled,
4549
}: BrowserAutomationsListProps) {
4650
const [expandedId, setExpandedId] = useState<string | null>(null);
4751
const { hasPermission } = usePermissions();
4852
const canCreateIntegration = hasPermission('integration', 'create');
4953
const canUpdateIntegration = hasPermission('integration', 'update');
5054

51-
const nextRun = automations.length > 0 ? getNextScheduledRun() : null;
55+
const hasEnabledAutomations = automations.some((a) => a.isEnabled);
56+
const nextRun = hasEnabledAutomations ? getNextScheduledRun() : null;
5257

5358
return (
5459
<div className="rounded-lg border border-border bg-card overflow-hidden">
@@ -102,6 +107,8 @@ export function BrowserAutomationsList({
102107
}
103108
onRun={() => onRun(automation.id)}
104109
onEdit={() => onEditClick(automation)}
110+
onDelete={() => onDelete(automation.id)}
111+
onToggleEnabled={(enabled) => onToggleEnabled(automation.id, enabled)}
105112
/>
106113
))}
107114
</div>

apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/useBrowserAutomations.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,45 @@ export function useBrowserAutomations({ taskId }: UseBrowserAutomationsOptions)
8787
[fetchAutomations],
8888
);
8989

90+
const deleteAutomation = useCallback(
91+
async (automationId: string) => {
92+
try {
93+
const res = await apiClient.delete(`/v1/browserbase/automations/${automationId}`);
94+
if (res.error) throw new Error(res.error);
95+
toast.success('Browser automation deleted');
96+
await fetchAutomations();
97+
} catch (err) {
98+
toast.error(err instanceof Error ? err.message : 'Failed to delete automation');
99+
}
100+
},
101+
[fetchAutomations],
102+
);
103+
104+
const toggleAutomation = useCallback(
105+
async (automationId: string, isEnabled: boolean) => {
106+
try {
107+
const res = await apiClient.patch<BrowserAutomation>(
108+
`/v1/browserbase/automations/${automationId}`,
109+
{ isEnabled },
110+
);
111+
if (res.error) throw new Error(res.error);
112+
toast.success(isEnabled ? 'Automation enabled' : 'Automation disabled');
113+
await fetchAutomations();
114+
} catch (err) {
115+
toast.error(err instanceof Error ? err.message : 'Failed to update automation');
116+
}
117+
},
118+
[fetchAutomations],
119+
);
120+
90121
return {
91122
automations,
92123
isLoading,
93124
isSaving,
94125
fetchAutomations,
95126
createAutomation,
96127
updateAutomation,
128+
deleteAutomation,
129+
toggleAutomation,
97130
};
98131
}

0 commit comments

Comments
 (0)