support reasoning agentid by accountId or session for cron (#847)
This commit is contained in:
committed by
GitHub
Unverified
parent
54ec784545
commit
758a8f8c94
@@ -251,7 +251,6 @@ function TaskDialog({ job, configuredChannels, onClose, onSave }: TaskDialogProp
|
||||
const [name, setName] = useState(job?.name || '');
|
||||
const [message, setMessage] = useState(job?.message || '');
|
||||
const [selectedAgentId, setSelectedAgentId] = useState(job?.agentId || useChatStore.getState().currentAgentId);
|
||||
const [agentIdChanged, setAgentIdChanged] = useState(false);
|
||||
// Extract cron expression string from CronSchedule object or use as-is if string
|
||||
const initialSchedule = (() => {
|
||||
const s = job?.schedule;
|
||||
@@ -411,7 +410,7 @@ function TaskDialog({ job, configuredChannels, onClose, onSave }: TaskDialogProp
|
||||
schedule: finalSchedule,
|
||||
delivery: finalDelivery,
|
||||
enabled,
|
||||
...(agentIdChanged ? { agentId: selectedAgentId } : {}),
|
||||
agentId: selectedAgentId,
|
||||
});
|
||||
onClose();
|
||||
toast.success(job ? t('toast.updated') : t('toast.created'));
|
||||
@@ -468,7 +467,6 @@ function TaskDialog({ job, configuredChannels, onClose, onSave }: TaskDialogProp
|
||||
value={selectedAgentId}
|
||||
onChange={(e) => {
|
||||
setSelectedAgentId(e.target.value);
|
||||
setAgentIdChanged(true);
|
||||
}}
|
||||
className="h-[44px] rounded-xl border-black/10 dark:border-white/10 bg-[#eeece3] dark:bg-muted text-[13px]"
|
||||
>
|
||||
|
||||
@@ -11,7 +11,7 @@ interface CronState {
|
||||
jobs: CronJob[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
|
||||
|
||||
// Actions
|
||||
fetchJobs: () => Promise<void>;
|
||||
createJob: (input: CronJobCreateInput) => Promise<CronJob>;
|
||||
@@ -26,7 +26,7 @@ export const useCronStore = create<CronState>((set) => ({
|
||||
jobs: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
|
||||
|
||||
fetchJobs: async () => {
|
||||
const currentJobs = useCronStore.getState().jobs;
|
||||
// Only show loading spinner when there's no data yet (stale-while-revalidate).
|
||||
@@ -39,49 +39,20 @@ export const useCronStore = create<CronState>((set) => ({
|
||||
try {
|
||||
const result = await hostApiFetch<CronJob[]>('/api/cron/jobs');
|
||||
|
||||
// If Gateway returned fewer jobs than we have, something might be wrong - preserve all known jobs
|
||||
// and just update agentIds from localStorage for the ones Gateway returned.
|
||||
// Priority: API agentId (if non-'main') > currentJobs > localStorage > 'main'
|
||||
const resultIds = new Set(result.map(j => j.id));
|
||||
const savedAgentIdMap = JSON.parse(localStorage.getItem('cronAgentIdMap') || '{}') as Record<string, string>;
|
||||
// Gateway now correctly returns agentId for all jobs.
|
||||
// If Gateway returned fewer jobs than we have (e.g. race condition), preserve
|
||||
// the extra ones from current state to avoid losing data.
|
||||
const resultIds = new Set(result.map((j) => j.id));
|
||||
const extraJobs = currentJobs.filter((j) => !resultIds.has(j.id));
|
||||
const allJobs = [...result, ...extraJobs];
|
||||
|
||||
// Update localStorage agentId map with current data
|
||||
const newAgentIdMap: Record<string, string> = {};
|
||||
|
||||
// For jobs returned by Gateway, restore agentId
|
||||
const jobsWithAgentId = result.map((job) => {
|
||||
// Priority: API response (if non-'main') > currentJobs > localStorage > default 'main'
|
||||
const existingJob = currentJobs.find((j) => j.id === job.id);
|
||||
const savedAgentId = savedAgentIdMap[job.id];
|
||||
let agentId = job.agentId;
|
||||
if (!agentId || agentId === 'main') {
|
||||
// API returned 'main' or nothing — use cached value
|
||||
if (existingJob && existingJob.agentId !== 'main') {
|
||||
agentId = existingJob.agentId;
|
||||
} else if (savedAgentId && savedAgentId !== 'main') {
|
||||
agentId = savedAgentId;
|
||||
} else {
|
||||
agentId = 'main';
|
||||
}
|
||||
}
|
||||
if (agentId !== 'main') {
|
||||
newAgentIdMap[job.id] = agentId;
|
||||
}
|
||||
return { ...job, agentId };
|
||||
});
|
||||
|
||||
// If Gateway returned fewer jobs, preserve extra jobs from current state
|
||||
const extraJobs = currentJobs.filter(j => !resultIds.has(j.id));
|
||||
const allJobs = [...jobsWithAgentId, ...extraJobs];
|
||||
|
||||
localStorage.setItem('cronAgentIdMap', JSON.stringify(newAgentIdMap));
|
||||
set({ jobs: allJobs, loading: false });
|
||||
} catch (error) {
|
||||
// Preserve previous jobs on error so the user sees stale data instead of nothing.
|
||||
set({ error: String(error), loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
createJob: async (input) => {
|
||||
try {
|
||||
// Auto-capture currentAgentId if not provided
|
||||
@@ -90,59 +61,23 @@ export const useCronStore = create<CronState>((set) => ({
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ ...input, agentId }),
|
||||
});
|
||||
const jobWithAgentId = { ...job, agentId };
|
||||
// Persist agentId to localStorage (since Gateway doesn't return it)
|
||||
const savedMap = JSON.parse(localStorage.getItem('cronAgentIdMap') || '{}') as Record<string, string>;
|
||||
savedMap[jobWithAgentId.id] = agentId;
|
||||
localStorage.setItem('cronAgentIdMap', JSON.stringify(savedMap));
|
||||
set((state) => ({ jobs: [...state.jobs, jobWithAgentId] }));
|
||||
return jobWithAgentId;
|
||||
set((state) => ({ jobs: [...state.jobs, job] }));
|
||||
return job;
|
||||
} catch (error) {
|
||||
console.error('Failed to create cron job:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
updateJob: async (id, input) => {
|
||||
try {
|
||||
const currentJob = useCronStore.getState().jobs.find((j) => j.id === id);
|
||||
const newAgentId = input.agentId;
|
||||
|
||||
// If agentId changed, recreate with new agentId first then delete old one (Gateway doesn't support updating sessionTarget)
|
||||
if (newAgentId && currentJob && newAgentId !== currentJob.agentId) {
|
||||
// Create new job with new agentId first (preserves schedule on failure)
|
||||
const { agentId: _agentId, ...restInput } = input;
|
||||
const newJob = await hostApiFetch<CronJob>('/api/cron/jobs', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ ...restInput, agentId: newAgentId }),
|
||||
});
|
||||
const jobWithAgentId = { ...currentJob, ...newJob, agentId: newAgentId };
|
||||
// Update localStorage: add new id first, then remove old id
|
||||
const savedMap = JSON.parse(localStorage.getItem('cronAgentIdMap') || '{}') as Record<string, string>;
|
||||
savedMap[jobWithAgentId.id] = newAgentId;
|
||||
localStorage.setItem('cronAgentIdMap', JSON.stringify(savedMap));
|
||||
// Delete old job after new one is created successfully
|
||||
await hostApiFetch(`/api/cron/jobs/${encodeURIComponent(id)}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
delete savedMap[id];
|
||||
localStorage.setItem('cronAgentIdMap', JSON.stringify(savedMap));
|
||||
set((state) => ({
|
||||
jobs: state.jobs.map((j) => (j.id === id ? jobWithAgentId : j)),
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal update for other fields - use currentJob as base, overlay updatedJob to preserve fields
|
||||
const updatedJob = await hostApiFetch<CronJob>(`/api/cron/jobs/${encodeURIComponent(id)}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
// Merge: updatedJob fields override currentJob, but preserve currentJob fields not in updatedJob
|
||||
const jobWithAgentId = { ...currentJob, ...updatedJob, agentId: currentJob?.agentId ?? updatedJob.agentId };
|
||||
set((state) => ({
|
||||
jobs: state.jobs.map((job) =>
|
||||
job.id === id ? jobWithAgentId : job
|
||||
job.id === id ? updatedJob : job
|
||||
),
|
||||
}));
|
||||
} catch (error) {
|
||||
@@ -150,16 +85,12 @@ export const useCronStore = create<CronState>((set) => ({
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
deleteJob: async (id) => {
|
||||
try {
|
||||
await hostApiFetch(`/api/cron/jobs/${encodeURIComponent(id)}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
// Remove from localStorage
|
||||
const savedMap = JSON.parse(localStorage.getItem('cronAgentIdMap') || '{}') as Record<string, string>;
|
||||
delete savedMap[id];
|
||||
localStorage.setItem('cronAgentIdMap', JSON.stringify(savedMap));
|
||||
set((state) => ({
|
||||
jobs: state.jobs.filter((job) => job.id !== id),
|
||||
}));
|
||||
@@ -168,7 +99,7 @@ export const useCronStore = create<CronState>((set) => ({
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
toggleJob: async (id, enabled) => {
|
||||
try {
|
||||
await hostApiFetch('/api/cron/toggle', {
|
||||
@@ -185,7 +116,7 @@ export const useCronStore = create<CronState>((set) => ({
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
triggerJob: async (id) => {
|
||||
try {
|
||||
await hostApiFetch('/api/cron/trigger', {
|
||||
@@ -194,14 +125,8 @@ export const useCronStore = create<CronState>((set) => ({
|
||||
});
|
||||
// Refresh jobs after trigger to update lastRun/nextRun state
|
||||
try {
|
||||
const currentJobs = useCronStore.getState().jobs;
|
||||
const resultJobs = await hostApiFetch<CronJob[]>('/api/cron/jobs');
|
||||
// Preserve agentId from existing jobs
|
||||
const jobsWithAgentId = resultJobs.map((job) => {
|
||||
const existing = currentJobs.find((j) => j.id === job.id);
|
||||
return existing ? { ...job, agentId: existing.agentId } : job;
|
||||
});
|
||||
set({ jobs: jobsWithAgentId });
|
||||
const result = await hostApiFetch<CronJob[]>('/api/cron/jobs');
|
||||
set({ jobs: result });
|
||||
} catch {
|
||||
// Ignore refresh error
|
||||
}
|
||||
@@ -210,6 +135,6 @@ export const useCronStore = create<CronState>((set) => ({
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
setJobs: (jobs) => set({ jobs }),
|
||||
}));
|
||||
|
||||
@@ -17,6 +17,7 @@ const LOAD_SESSIONS_MIN_INTERVAL_MS = 1_200;
|
||||
const LOAD_HISTORY_MIN_INTERVAL_MS = 800;
|
||||
let lastLoadSessionsAt = 0;
|
||||
let lastLoadHistoryAt = 0;
|
||||
let cronRepairTriggeredThisSession = false;
|
||||
|
||||
interface GatewayHealth {
|
||||
ok: boolean;
|
||||
@@ -262,6 +263,17 @@ export const useGatewayStore = create<GatewayState>((set, get) => ({
|
||||
const unsubscribers: Array<() => void> = [];
|
||||
unsubscribers.push(subscribeHostEvent<GatewayStatus>('gateway:status', (payload) => {
|
||||
set({ status: payload });
|
||||
|
||||
// Trigger cron repair when gateway becomes ready
|
||||
if (!cronRepairTriggeredThisSession && payload.state === 'running') {
|
||||
cronRepairTriggeredThisSession = true;
|
||||
// Fire-and-forget: fetch cron jobs to trigger repair logic in background
|
||||
import('./cron')
|
||||
.then(({ useCronStore }) => {
|
||||
useCronStore.getState().fetchJobs();
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
}));
|
||||
unsubscribers.push(subscribeHostEvent<{ message?: string }>('gateway:error', (payload) => {
|
||||
set({ lastError: payload.message || 'Gateway error' });
|
||||
|
||||
Reference in New Issue
Block a user