diff --git a/src/task_scheduler.py b/src/task_scheduler.py index 9c7ac754e..c2398647f 100644 --- a/src/task_scheduler.py +++ b/src/task_scheduler.py @@ -1712,8 +1712,15 @@ class TaskScheduler: target = (output or "").strip() explicit = "" + account_id = "" if target.startswith("email:"): explicit = target.split(":", 1)[1].strip() + if "|account=" in explicit: + explicit, account_id = explicit.split("|account=", 1) + explicit = explicit.strip() + account_id = account_id.strip() + if explicit == "self": + explicit = "" elif "@" in target: explicit = target @@ -1721,7 +1728,7 @@ class TaskScheduler: from routes.email_routes import _resolve_send_config from routes.email_helpers import _send_smtp_message - cfg = _resolve_send_config(owner=task.owner or "") + cfg = _resolve_send_config(account_id=account_id or None, owner=task.owner or "") to_addr = explicit or cfg.get("from_address") or cfg.get("smtp_user") or "" if not to_addr: raise RuntimeError("No email recipient resolved for task output") diff --git a/static/js/tasks.js b/static/js/tasks.js index df0f62e4a..c8ae8feb3 100644 --- a/static/js/tasks.js +++ b/static/js/tasks.js @@ -254,6 +254,34 @@ function _taskPromptConfig(prompt) { } } +function _parseTaskEmailOutputTarget(output) { + const raw = String(output || '').trim(); + if (!raw) return { enabled: false, to: '', accountId: '' }; + if (raw === 'email') return { enabled: true, to: '', accountId: '' }; + if (/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(raw)) return { enabled: true, to: raw, accountId: '' }; + if (!raw.startsWith('email:')) return { enabled: false, to: '', accountId: '' }; + let payload = raw.slice('email:'.length).trim(); + let accountId = ''; + const marker = '|account='; + const markerIdx = payload.indexOf(marker); + if (markerIdx >= 0) { + accountId = payload.slice(markerIdx + marker.length).trim(); + payload = payload.slice(0, markerIdx).trim(); + } + return { + enabled: true, + to: payload && payload !== 'self' ? payload : '', + accountId, + }; +} + +function _buildTaskEmailOutputTarget(to, accountId) { + const cleanTo = String(to || '').trim(); + const cleanAccount = String(accountId || '').trim(); + const base = `email:${cleanTo || 'self'}`; + return cleanAccount ? `${base}|account=${cleanAccount}` : (cleanTo ? base : 'email'); +} + async function _renderEmailActionOptions(action, existing, extra) { if (!_EMAIL_ACCOUNT_ACTIONS.has(action)) return; const accounts = (await _fetchEmailAccountsForTasks()).filter(a => a && a.enabled !== false); @@ -1141,6 +1169,7 @@ function _showForm(existing, initTaskType, initTriggerType) { +
${options} + + + +
Leave To blank to send to the selected account’s own address.
+ `; + }; + _fetchOutputTargets().then(targets => { const outputSel = document.getElementById('task-form-output'); if (!outputSel || targets.length <= 1) return; outputSel.innerHTML = ''; + const existingEmailOutput = _parseTaskEmailOutputTarget(existing?.output_target || ''); let matchedOutput = false; for (const t of targets) { const opt = document.createElement('option'); opt.value = t.value; opt.textContent = t.label; - if (existing?.output_target === t.value) { + if (existingEmailOutput.enabled && t.value === 'email') { + opt.selected = true; + matchedOutput = true; + } else if (!existingEmailOutput.enabled && existing?.output_target === t.value) { opt.selected = true; matchedOutput = true; } outputSel.appendChild(opt); } - if (existing?.output_target && !matchedOutput) { + if (existing?.output_target && !matchedOutput && !existingEmailOutput.enabled) { const opt = document.createElement('option'); opt.value = existing.output_target; opt.textContent = existing.output_target.includes('@') ? `Email: ${existing.output_target}` : existing.output_target; opt.selected = true; outputSel.appendChild(opt); } + outputSel.addEventListener('change', renderOutputExtra); + renderOutputExtra(); }); // Populate model dropdown from /api/models. Value is "endpoint_url::model" @@ -1517,7 +1588,13 @@ function _showForm(existing, initTaskType, initTriggerType) { // Save document.getElementById('task-form-save').addEventListener('click', async () => { const nameEl = document.getElementById('task-form-name'); - const outputTarget = document.getElementById('task-form-output')?.value || 'session'; + const outputSelValue = document.getElementById('task-form-output')?.value || 'session'; + let outputTarget = outputSelValue; + if (outputSelValue === 'email') { + const to = document.getElementById('task-form-output-email-to')?.value || ''; + const accountId = document.getElementById('task-form-output-email-account')?.value || ''; + outputTarget = _buildTaskEmailOutputTarget(to, accountId); + } const payload = { task_type: taskType, diff --git a/static/style.css b/static/style.css index 9e9c83e20..b81e692e4 100644 --- a/static/style.css +++ b/static/style.css @@ -23427,6 +23427,8 @@ body:not(.welcome-ready) #welcome-screen { font-variant-numeric: tabular-nums; } .task-log-force-run { + appearance: none; + box-sizing: border-box; border: 0; background: color-mix(in srgb, var(--fg) 7%, transparent); box-shadow: none; @@ -23434,7 +23436,9 @@ body:not(.welcome-ready) #welcome-screen { opacity: .82; margin-left: 7px; padding: 1px 6px 1px 4px; + height: 18px; min-height: 16px; + max-height: 18px; border-radius: 999px; display: inline-flex; align-items: center;