Polish schedules list and editing (#1860)

* feat(schedules): polish schedules list and editing

Adds host filtering, status grouping, and target resolution to the
schedules screen. Extracts a shared HostFilter from sessions. Improves
schedule editing with project targets and model hydration. Handles
missing targets server-side instead of retrying forever.

* test(mcp): update schedule mocks for createOrReplace

* fix(schedules): only show host name when multiple hosts exist
This commit is contained in:
Mohamed Boudra
2026-07-02 12:05:35 +02:00
committed by GitHub
parent 2263469342
commit 6513b56571
21 changed files with 1981 additions and 551 deletions
@@ -1,8 +1,9 @@
import type { DaemonClient } from "@getpaseo/client/internal/daemon-client";
import type { ScheduleSummary } from "@getpaseo/protocol/schedule/types";
import { isNewAgentSchedule } from "@/utils/schedule-format";
import { toErrorMessage } from "@/utils/error-messages";
export const ALL_SCHEDULE_HOSTS_FAILED_MESSAGE = "No connected hosts could load schedules";
export interface ScheduleHostInput {
serverId: string;
serverName: string;
@@ -17,65 +18,76 @@ export interface ScheduleRuntime {
getSnapshot(serverId: string): ScheduleRuntimeSnapshot | null | undefined;
}
export interface ScheduleHostSection {
/** A schedule tagged with the host it came from, so the flat list can render a
* per-row host label and scope mutations without host sections. */
export interface AggregatedSchedule extends ScheduleSummary {
serverId: string;
serverName: string;
isOnline: boolean;
schedules: ScheduleSummary[];
error: string | null;
}
export interface FetchAggregatedSchedulesInput {
hosts: ScheduleHostInput[];
runtime: ScheduleRuntime;
export interface ScheduleHostError {
serverId: string;
serverName: string;
message: string;
}
export interface FetchAggregatedSchedulesResult {
sections: ScheduleHostSection[];
schedules: AggregatedSchedule[];
hostErrors: ScheduleHostError[];
}
export interface FetchAggregatedSchedulesInput {
hosts: readonly ScheduleHostInput[];
runtime: ScheduleRuntime;
}
/**
* Fetch schedules across connected hosts and merge them into one flat list.
* Connectivity is checked here at execution time (not pre-filtered by the hook)
* so the query — retried as the runtime version changes — reliably picks a host
* up the moment it comes online, including on a cold deep-link.
*
* Offline hosts are skipped. A connected host that fails contributes to
* `hostErrors` (surfaced as a banner) while the rest still render; only when
* every connected host fails do we throw so the screen shows a full error.
*/
export async function fetchAggregatedSchedules(
input: FetchAggregatedSchedulesInput,
): Promise<FetchAggregatedSchedulesResult> {
const sections = await Promise.all(
input.hosts.map(async (host): Promise<ScheduleHostSection> => {
const schedules: AggregatedSchedule[] = [];
const hostErrors: ScheduleHostError[] = [];
let connectedAttempts = 0;
await Promise.all(
input.hosts.map(async (host) => {
const snapshot = input.runtime.getSnapshot(host.serverId);
const isOnline = snapshot?.connectionStatus === "online";
const client = input.runtime.getClient(host.serverId);
if (!client || !isOnline) {
return {
serverId: host.serverId,
serverName: host.serverName,
isOnline,
schedules: [],
error: null,
};
return;
}
connectedAttempts += 1;
try {
const payload = await client.scheduleList();
if (payload.error) {
throw new Error(payload.error);
}
return {
serverId: host.serverId,
serverName: host.serverName,
isOnline,
schedules: payload.schedules.filter(isNewAgentSchedule),
error: null,
};
for (const schedule of payload.schedules) {
schedules.push({ ...schedule, serverId: host.serverId, serverName: host.serverName });
}
} catch (error) {
return {
hostErrors.push({
serverId: host.serverId,
serverName: host.serverName,
isOnline,
schedules: [],
error: toErrorMessage(error),
};
message: toErrorMessage(error),
});
}
}),
);
return { sections };
if (connectedAttempts > 0 && schedules.length === 0 && hostErrors.length === connectedAttempts) {
throw new Error(ALL_SCHEDULE_HOSTS_FAILED_MESSAGE);
}
return { schedules, hostErrors };
}