Skip to content

Commit 97f71b5

Browse files
jayleatonclaude
andauthored
feat(web,mobile): one scheduled-task list and editor for agents and threads (#226)
* feat(web,mobile): one scheduled-task list and editor for agents and threads Settings and Agents → Scheduled now share one list with an All / Agents / Threads / New thread / Archived filter and one editor that can pick an agent and edit cron schedules. Mobile gets the same filter, agent destination and cron editing. The thread Automations section can start a task bound to that thread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web): hide the thread Automations section until the thread has a task Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server): leaving an agent no longer keeps the agent's old thread Moving an agent task to Continue or Fresh session with no thread chosen sent profileId: null and threadId: null, and the save coalesced the null back to the agent's thread. Later runs then went into that conversation with its model and modes. Leaving an agent with an explicit null thread now clears the binding; an omitted threadId still keeps it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: JJ Eaton <58495321+oldbettie@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent aa65f1b commit 97f71b5

24 files changed

Lines changed: 1557 additions & 1095 deletions

‎apps/mobile/src/Stack.tsx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ import {
110110
ScheduledTaskModelPickerRouteScreen,
111111
ScheduledTaskBranchPickerRouteScreen,
112112
ScheduledTaskThreadPickerRouteScreen,
113+
ScheduledTaskAgentPickerRouteScreen,
113114
} from "./features/settings/ScheduledTaskPickerScreens";
114115
import { SettingsKeyboardRouteScreen } from "./features/settings/SettingsKeyboardRouteScreen";
115116
import { SettingsLegalRouteScreen } from "./features/settings/SettingsLegalRouteScreen";
@@ -305,6 +306,10 @@ const SETTINGS_SCREENS = {
305306
screen: ScheduledTaskThreadPickerRouteScreen,
306307
options: settingsPage("Destination"),
307308
}),
309+
SettingsScheduledTaskAgent: createNativeStackScreen({
310+
screen: ScheduledTaskAgentPickerRouteScreen,
311+
options: settingsPage("Agent"),
312+
}),
308313
SettingsScheduledTaskModel: createNativeStackScreen({
309314
screen: ScheduledTaskModelPickerRouteScreen,
310315
options: THREAD_SETTINGS_ROUTE,

‎apps/mobile/src/features/settings/ScheduledTaskPickerScreens.tsx‎

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,13 @@ import {
2424
useScheduledTaskDestinationLabel,
2525
useScheduledTaskEditor,
2626
} from "./scheduled-task-editor";
27-
import { moveDraftToThread, unbindDraftFromThread } from "./scheduledTaskDraft";
27+
import { agentModelLabel, useEnvironmentAgents } from "../../state/agents";
28+
import {
29+
moveDraftToAgent,
30+
moveDraftToThread,
31+
schedulableAgentProfiles,
32+
unbindDraftFromThread,
33+
} from "./scheduledTaskDraft";
2834

2935
function MissingTaskDraft() {
3036
return (
@@ -281,3 +287,62 @@ export function ScheduledTaskThreadPickerRouteScreen() {
281287
</SettingsScreen>
282288
);
283289
}
290+
291+
export function ScheduledTaskAgentPickerRouteScreen() {
292+
const navigation = useNavigation();
293+
const insets = useSafeAreaInsets();
294+
const { editor, setEditor } = useScheduledTaskEditor();
295+
const agents = useEnvironmentAgents(editor?.environmentId ?? null);
296+
297+
if (!editor) return <MissingTaskDraft />;
298+
299+
const profiles =
300+
agents.status === "ready"
301+
? schedulableAgentProfiles(agents.profiles, editor.environmentId)
302+
: [];
303+
const selectedId = editor.draft.profileId;
304+
305+
return (
306+
<SettingsScreen title="Agent">
307+
<FlatList
308+
data={profiles}
309+
keyExtractor={(profile) => profile.profileId}
310+
className="flex-1"
311+
contentInsetAdjustmentBehavior="automatic"
312+
showsVerticalScrollIndicator={false}
313+
contentContainerStyle={{
314+
paddingHorizontal: 16,
315+
paddingTop: 16,
316+
paddingBottom: Math.max(insets.bottom, 16) + 16,
317+
}}
318+
ListEmptyComponent={
319+
<Text className="p-5 text-center text-sm text-foreground-muted">
320+
{agents.status === "loading"
321+
? "Loading agents…"
322+
: "No agents on this environment can run scheduled tasks."}
323+
</Text>
324+
}
325+
renderItem={({ item, index }) => (
326+
<SelectionRow
327+
title={item.name}
328+
subtitle={agentModelLabel(item) ?? undefined}
329+
selected={item.profileId === selectedId}
330+
isFirst={index === 0}
331+
isLast={index === profiles.length - 1}
332+
onPress={() => {
333+
void Haptics.selectionAsync();
334+
if (item.profileId !== selectedId) {
335+
setEditor((current) =>
336+
current
337+
? { ...current, draft: moveDraftToAgent(current.draft, item.profileId) }
338+
: current,
339+
);
340+
}
341+
navigation.goBack();
342+
}}
343+
/>
344+
)}
345+
/>
346+
</SettingsScreen>
347+
);
348+
}

0 commit comments

Comments
 (0)