// ========================================== // Chat Switchboard — Task Settings (User) // ========================================== // Settings → Tasks section. CRUD for personal tasks with schedule builder, // persona/model picker, budget config, and starter templates. // Loaded on the settings surface. const PRESETS = [ { label: 'Every morning (6am)', cron: '0 6 * * *' }, { label: 'Weekday mornings (8am)', cron: '0 8 * * 1-5' }, { label: 'Every hour', cron: '0 * * * *' }, { label: 'Weekly (Monday 9am)', cron: '0 9 * * 1' }, { label: 'Monthly (1st midnight)', cron: '0 0 1 * *' }, { label: 'Webhook trigger', cron: 'webhook' }, { label: 'Custom...', cron: '' }, ]; const TEMPLATES = [ { name: 'Morning News Digest', prompt: 'Summarize the top 5 tech news headlines from today. Be concise — one paragraph per story.', schedule: '0 6 * * *' }, { name: 'Daily Standup Prep', prompt: 'Review my recent notes and conversations. Generate 3 concise standup talking points covering what I worked on yesterday, what I plan today, and any blockers.', schedule: '0 8 * * 1-5' }, { name: 'Weekly Project Summary', prompt: 'Summarize this week\'s activity across my projects. Highlight completed items, open threads, and priorities for next week.', schedule: '0 17 * * 5' }, { name: 'Stock Watchlist Check', prompt: 'Check for unusual pre-market activity on major tech stocks (AAPL, GOOGL, MSFT, NVDA, AMZN). Flag any moves > 2%.', schedule: '0 9 * * 1-5' }, { name: 'Research Digest', prompt: 'Search for the latest papers and blog posts on AI agents and autonomous systems. Summarize the 3 most interesting findings.', schedule: '0 12 * * 1' }, ]; function esc(s) { return s ? s.replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"') : ''; } // Detect browser timezone function browserTZ() { try { return Intl.DateTimeFormat().resolvedOptions().timeZone; } catch (_) { return 'UTC'; } } // ── Task list ──────────────────────────── async function loadTaskList() { var mount = document.getElementById('settingsTasksMount'); if (!mount) return; try { // Load personal tasks var resp = await API._get('/api/v1/tasks'); var personalTasks = (resp.data || []).map(function(t) { t._source = 'personal'; return t; }); // Load team tasks from all user's teams var teamTasks = []; try { var teamsResp = await API._get('/api/v1/teams/mine'); var teams = teamsResp.data || teamsResp || []; for (var i = 0; i < teams.length; i++) { try { var tr = await API._get('/api/v1/teams/' + teams[i].id + '/tasks'); (tr.data || []).forEach(function(t) { t._source = 'team'; t._teamName = teams[i].name; teamTasks.push(t); }); } catch (_) { /* team may not have tasks */ } } } catch (_) { /* no teams */ } // Deduplicate (user might own a team task) var seen = {}; personalTasks.forEach(function(t) { seen[t.id] = true; }); teamTasks = teamTasks.filter(function(t) { return !seen[t.id]; }); var tasks = personalTasks.concat(teamTasks); var html = '
' + '

Your scheduled tasks. Tasks run automatically on their schedule.

' + '' + '
'; if (tasks.length > 0) { html += tasks.map(function(t) { var status = t.is_active ? 'active' : 'paused'; var teamBadge = t._source === 'team' ? '' + esc(t._teamName || 'team') + '' : ''; var sched = t.schedule === 'once' ? 'One-shot' : t.schedule === 'webhook' ? 'Webhook trigger' : t.schedule; var lastStatus = ''; if (t.last_run_at) { lastStatus = ' \u00b7 last: ' + new Date(t.last_run_at).toLocaleString(); } var nextRun = t.schedule === 'webhook' ? 'on trigger' : t.next_run_at ? new Date(t.next_run_at).toLocaleString() : '\u2014'; var triggerBtn = ''; if (t.schedule === 'webhook' && t.trigger_token) { triggerBtn = ''; } return '
' + '
' + '
' + '
' + esc(t.name) + '
' + '
' + esc(sched) + ' (' + esc(t.timezone) + ')' + lastStatus + '
' + '
next: ' + nextRun + '
' + '
' + '
' + teamBadge + status + triggerBtn + '' + '' + '' + '
' + '
' + '
'; }).join(''); } // Templates section html += '

Starter Templates

' + '

Quick-start with a pre-configured task. You can customize after creation.

' + '
'; TEMPLATES.forEach(function(tmpl) { html += '
' + '
' + esc(tmpl.name) + '
' + '
' + esc(tmpl.schedule) + '
' + '
'; }); html += '
'; mount.innerHTML = html; // Wire buttons document.getElementById('taskNewBtn').addEventListener('click', function() { showCreateForm(); }); mount.querySelectorAll('.task-run-btn').forEach(function(btn) { btn.addEventListener('click', function() { runTask(btn.dataset.id); }); }); mount.querySelectorAll('.task-toggle-btn').forEach(function(btn) { btn.addEventListener('click', function() { toggleTask(btn.dataset.id, btn.dataset.active === 'true'); }); }); mount.querySelectorAll('.task-del-btn').forEach(function(btn) { btn.addEventListener('click', function() { deleteTask(btn.dataset.id); }); }); mount.querySelectorAll('.task-trigger-btn').forEach(function(btn) { btn.addEventListener('click', function() { var url = location.origin + (window.__BASE__ || '') + '/api/v1/hooks/t/' + btn.dataset.token; navigator.clipboard.writeText(url).then(function() { UI.toast('Trigger URL copied', 'success'); }).catch(function() { prompt('Trigger URL:', url); }); }); }); } catch (err) { mount.innerHTML = '

Failed to load tasks: ' + esc(err.message) + '

'; } } // ── Create form ────────────────────────── function showCreateForm(defaults) { var d = defaults || {}; var mount = document.getElementById('settingsTasksMount'); if (!mount) return; var schedOpts = PRESETS.map(function(p) { var sel = (d.schedule && d.schedule === p.cron) ? ' selected' : ''; return ''; }).join(''); mount.innerHTML = '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '' + '

Used to sign outbound webhook payloads. Leave blank for auto-generated secret.

' + '
' + '' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '' + '' + '
' + '' + '
'; document.getElementById('taskBackBtn').addEventListener('click', loadTaskList); document.getElementById('taskCreateBtn').addEventListener('click', createTask); // Toggle custom cron visibility var preset = document.getElementById('taskPreset'); var customGroup = document.getElementById('taskCustomCronGroup'); var triggerInfo = document.getElementById('taskWebhookTriggerInfo'); preset.addEventListener('change', function() { var isWebhook = preset.value === 'webhook'; var isCustom = preset.value === ''; customGroup.style.display = isCustom ? '' : 'none'; triggerInfo.style.display = isWebhook ? '' : 'none'; if (!isWebhook && !isCustom) document.getElementById('taskCron').value = preset.value; if (isWebhook) document.getElementById('taskCron').value = 'webhook'; }); // Initial state customGroup.style.display = preset.value === '' ? '' : 'none'; triggerInfo.style.display = preset.value === 'webhook' ? '' : 'none'; if (preset.value !== '' && preset.value !== 'webhook') document.getElementById('taskCron').value = preset.value; // Toggle webhook URL and secret visibility var output = document.getElementById('taskOutput'); var webhookGroup = document.getElementById('taskWebhookGroup'); var webhookSecretGroup = document.getElementById('taskWebhookSecretGroup'); output.addEventListener('change', function() { var isWH = output.value === 'webhook'; webhookGroup.style.display = isWH ? '' : 'none'; webhookSecretGroup.style.display = isWH ? '' : 'none'; }); } async function createTask() { var schedule = document.getElementById('taskCron').value || document.getElementById('taskPreset').value; if (!schedule) { UI.toast('Schedule is required', 'error'); return; } var payload = { name: document.getElementById('taskName').value, task_type: 'prompt', user_prompt: document.getElementById('taskPrompt').value, schedule: schedule, timezone: document.getElementById('taskTZ').value || 'UTC', output_mode: document.getElementById('taskOutput').value, notify_on_complete: document.getElementById('taskNotifyComplete').checked, notify_on_failure: document.getElementById('taskNotifyFail').checked, }; var model = document.getElementById('taskModel').value; if (model) payload.model_id = model; var maxTokens = parseInt(document.getElementById('taskMaxTokens').value); if (maxTokens > 0) payload.max_tokens = maxTokens; var maxTools = parseInt(document.getElementById('taskMaxToolCalls').value); if (maxTools > 0) payload.max_tool_calls = maxTools; var webhookURL = document.getElementById('taskWebhookURL')?.value; if (payload.output_mode === 'webhook' && webhookURL) { payload.webhook_url = webhookURL; } var webhookSecret = document.getElementById('taskWebhookSecret')?.value; if (payload.output_mode === 'webhook' && webhookSecret) { payload.webhook_secret = webhookSecret; } try { var created = await API._post('/api/v1/tasks', payload); UI.toast('Task created', 'success'); // Show trigger URL for webhook-scheduled tasks if (created.schedule === 'webhook' && created.trigger_token) { var triggerURL = location.origin + (window.__BASE__ || '') + '/api/v1/hooks/t/' + created.trigger_token; UI.modal('Webhook Trigger URL', '

POST to this URL to trigger the task. ' + 'You can use this in CI pipelines, cron jobs, or as another task\\u2019s webhook URL for chaining.

' + '
' + '' + '' + '
' + '

The request body is passed to the task as trigger_payload. ' + 'For task-to-task chaining: set Task A\\u2019s webhook_url to Task B\\u2019s trigger URL.

', [{ label: 'Done', variant: 'primary' }] ); setTimeout(function() { var copyBtn = document.getElementById('_copyTriggerBtn'); if (copyBtn) copyBtn.addEventListener('click', function() { var input = document.getElementById('_triggerURLInput'); if (input) { navigator.clipboard.writeText(input.value); UI.toast('Copied', 'success'); } }); }, 100); } loadTaskList(); if (typeof TaskSidebar !== 'undefined') TaskSidebar.refresh(); } catch (err) { UI.toast(err.message || 'Failed to create task', 'error'); } } // ── Actions ────────────────────────────── async function runTask(id) { try { await API._post('/api/v1/tasks/' + id + '/run', {}); UI.toast('Task scheduled for immediate run', 'success'); } catch (err) { UI.toast(err.message || 'Failed', 'error'); } } async function toggleTask(id, isActive) { try { await API._put('/api/v1/tasks/' + id, { is_active: !isActive }); UI.toast(isActive ? 'Task paused' : 'Task resumed', 'success'); loadTaskList(); if (typeof TaskSidebar !== 'undefined') TaskSidebar.refresh(); } catch (err) { UI.toast(err.message || 'Failed', 'error'); } } async function deleteTask(id) { if (!confirm('Delete this task?')) return; try { await API._delete('/api/v1/tasks/' + id); UI.toast('Task deleted', 'success'); loadTaskList(); if (typeof TaskSidebar !== 'undefined') TaskSidebar.refresh(); } catch (err) { UI.toast(err.message || 'Failed', 'error'); } } // Template launcher sb.register('_createFromTemplate', function(name) { var tmpl = TEMPLATES.find(function(t) { return t.name === name; }); if (tmpl) showCreateForm({ name: tmpl.name, prompt: tmpl.prompt, schedule: tmpl.schedule }); }); // ── Entry point (called from settings loader) ── sb.register('_loadSettingsTasks', function() { loadTaskList(); });