clementine-agent 1.18.78 → 1.18.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/dashboard.js +212 -1
- package/package.json +1 -1
package/dist/cli/dashboard.js
CHANGED
|
@@ -15119,6 +15119,11 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
15119
15119
|
/* ── Recent history row hover (Tasks page bottom zone) ── */
|
|
15120
15120
|
.history-row { transition: background 0.12s ease; }
|
|
15121
15121
|
.history-row:hover { background: var(--bg-hover); }
|
|
15122
|
+
/* PRD Phase 1.2: "Run task once" running-state pulse on the Last run tab. */
|
|
15123
|
+
@keyframes pulse {
|
|
15124
|
+
0%, 100% { opacity: 0.4; transform: scale(0.85); }
|
|
15125
|
+
50% { opacity: 1; transform: scale(1); }
|
|
15126
|
+
}
|
|
15122
15127
|
/* ── Trick capability strip (skills + MCP + tools at a glance) ─── */
|
|
15123
15128
|
.task-cap-strip {
|
|
15124
15129
|
border-top: 1px solid var(--border-light);
|
|
@@ -19903,6 +19908,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
19903
19908
|
<div class="cron-tabs" role="tablist">
|
|
19904
19909
|
<button type="button" class="cron-tab-btn active" data-cron-tab="configure" onclick="switchCronTab('configure')">Configure</button>
|
|
19905
19910
|
<button type="button" class="cron-tab-btn" id="cron-tab-btn-preview" data-cron-tab="preview" onclick="switchCronTab('preview')" title="See exactly what the agent will receive at fire-time">What will run</button>
|
|
19911
|
+
<button type="button" class="cron-tab-btn" id="cron-tab-btn-lastrun" data-cron-tab="lastrun" onclick="switchCronTab('lastrun')" title="Watch the most recent run — click Run task once to fire it now">Last run</button>
|
|
19906
19912
|
</div>
|
|
19907
19913
|
<div class="modal-body">
|
|
19908
19914
|
<!-- ── Tab: Configure ─────────────────────────────────────────── -->
|
|
@@ -20222,10 +20228,23 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
20222
20228
|
</div>
|
|
20223
20229
|
</div>
|
|
20224
20230
|
</div><!-- /cron-tab-preview -->
|
|
20231
|
+
|
|
20232
|
+
<!-- ── Tab: Last run ── PRD Phase 1.2: "Run task once" inline output. -->
|
|
20233
|
+
<div class="cron-tab-pane" id="cron-tab-lastrun">
|
|
20234
|
+
<div id="cron-lastrun-body" style="padding:0">
|
|
20235
|
+
<div style="padding:36px 24px;color:var(--text-muted);text-align:center;font-size:13px">
|
|
20236
|
+
Save the task first, then click <strong>Run task once</strong> to fire it now and watch the result here.
|
|
20237
|
+
</div>
|
|
20238
|
+
</div>
|
|
20239
|
+
</div><!-- /cron-tab-lastrun -->
|
|
20225
20240
|
</div>
|
|
20226
20241
|
<div class="modal-footer">
|
|
20227
20242
|
<div style="display:flex;align-items:center;gap:8px;flex:1">
|
|
20228
20243
|
<button class="btn btn-sm" id="cron-train-btn" onclick="showCronTraining()" style="font-size:11px;display:none">Train with Agent</button>
|
|
20244
|
+
<!-- PRD §5.1 header bullet: "Run task once" green button. Visible only
|
|
20245
|
+
when editing a saved task (set by openEditCronModal). Disabled
|
|
20246
|
+
during an in-flight run. -->
|
|
20247
|
+
<button class="btn btn-sm btn-success" id="cron-run-once-btn" onclick="runCronOnceFromModal()" style="display:none;font-size:12px;padding:6px 14px">▶ Run task once</button>
|
|
20229
20248
|
</div>
|
|
20230
20249
|
<button onclick="closeCronModal()">Cancel</button>
|
|
20231
20250
|
<button class="btn-primary" id="cron-modal-save" onclick="saveCronJob()">Create Task</button>
|
|
@@ -24750,8 +24769,10 @@ function switchCronTab(tab) {
|
|
|
24750
24769
|
});
|
|
24751
24770
|
var configurePane = document.getElementById('cron-tab-configure');
|
|
24752
24771
|
var previewPane = document.getElementById('cron-tab-preview');
|
|
24772
|
+
var lastRunPane = document.getElementById('cron-tab-lastrun');
|
|
24753
24773
|
if (configurePane) configurePane.classList.toggle('active', tab === 'configure');
|
|
24754
24774
|
if (previewPane) previewPane.classList.toggle('active', tab === 'preview');
|
|
24775
|
+
if (lastRunPane) lastRunPane.classList.toggle('active', tab === 'lastrun');
|
|
24755
24776
|
if (tab === 'preview') {
|
|
24756
24777
|
var name = editingCronJob;
|
|
24757
24778
|
if (!name) {
|
|
@@ -24760,6 +24781,10 @@ function switchCronTab(tab) {
|
|
|
24760
24781
|
return;
|
|
24761
24782
|
}
|
|
24762
24783
|
if (_cronPreviewLoadedFor !== name) loadCronPreviewIntoTab(name);
|
|
24784
|
+
} else if (tab === 'lastrun') {
|
|
24785
|
+
// Re-render in case run-state changed since the modal opened.
|
|
24786
|
+
var jobLR = (typeof cronJobsData !== 'undefined' ? cronJobsData : []).find(function(j) { return j.name === editingCronJob; });
|
|
24787
|
+
if (jobLR) renderCronLastRunPane(jobLR);
|
|
24763
24788
|
}
|
|
24764
24789
|
}
|
|
24765
24790
|
|
|
@@ -24784,6 +24809,169 @@ async function loadCronPreviewIntoTab(jobName) {
|
|
|
24784
24809
|
// Mark the preview as stale (call after save so next tab visit refetches).
|
|
24785
24810
|
function markCronPreviewDirty() { _cronPreviewLoadedFor = null; }
|
|
24786
24811
|
|
|
24812
|
+
// ── PRD Phase 1.2: "Run task once" — inline run + Last run tab ───────────
|
|
24813
|
+
// Tracks an in-flight run triggered FROM the modal so the SSE listeners
|
|
24814
|
+
// know when a cron_complete event belongs to "the run I just kicked off"
|
|
24815
|
+
// vs a scheduled tick that fired in the background. Cleared when the run
|
|
24816
|
+
// completes or the modal closes.
|
|
24817
|
+
var _cronRunOnceInFlight = null; // { jobName: string, startedAt: number }
|
|
24818
|
+
var _cronRunOnceTickerId = null; // setInterval id for the elapsed counter
|
|
24819
|
+
|
|
24820
|
+
// Render the Last run pane from the job's most-recent JSONL entry. Called
|
|
24821
|
+
// when the modal opens for a saved task and when switchCronTab('lastrun')
|
|
24822
|
+
// reactivates the pane.
|
|
24823
|
+
function renderCronLastRunPane(job) {
|
|
24824
|
+
var pane = document.getElementById('cron-lastrun-body');
|
|
24825
|
+
if (!pane) return;
|
|
24826
|
+
// If we have an in-flight run, render the running state regardless of
|
|
24827
|
+
// what's on disk — the on-disk lastRun is from BEFORE this fire.
|
|
24828
|
+
if (_cronRunOnceInFlight && _cronRunOnceInFlight.jobName === (job && job.name)) {
|
|
24829
|
+
pane.innerHTML = renderCronRunningState(_cronRunOnceInFlight.startedAt);
|
|
24830
|
+
return;
|
|
24831
|
+
}
|
|
24832
|
+
var lr = job && job.lastRun;
|
|
24833
|
+
if (!lr) {
|
|
24834
|
+
pane.innerHTML = '<div style="padding:36px 24px;color:var(--text-muted);text-align:center;font-size:13px">No runs yet. Click <strong>Run task once</strong> below to fire it now and watch the result here.</div>';
|
|
24835
|
+
return;
|
|
24836
|
+
}
|
|
24837
|
+
pane.innerHTML = renderCronRunDetails(lr);
|
|
24838
|
+
}
|
|
24839
|
+
|
|
24840
|
+
function renderCronRunningState(startedAtMs) {
|
|
24841
|
+
var elapsed = Math.max(0, Math.round((Date.now() - startedAtMs) / 1000));
|
|
24842
|
+
return ''
|
|
24843
|
+
+ '<div style="padding:36px 24px;text-align:center">'
|
|
24844
|
+
+ '<div class="run-once-pulse" style="font-size:14px;color:var(--accent);font-weight:500;margin-bottom:8px">'
|
|
24845
|
+
+ '<span class="pulse-dot" style="display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--accent);margin-right:6px;animation:pulse 1.4s ease-in-out infinite"></span>'
|
|
24846
|
+
+ 'Running…'
|
|
24847
|
+
+ '</div>'
|
|
24848
|
+
+ '<div style="font-size:12px;color:var(--text-muted)">Elapsed: <span id="cron-run-once-elapsed">' + elapsed + 's</span></div>'
|
|
24849
|
+
+ '<div style="font-size:11px;color:var(--text-muted);margin-top:14px">Live output streaming will land here when the run completes.</div>'
|
|
24850
|
+
+ '</div>';
|
|
24851
|
+
}
|
|
24852
|
+
|
|
24853
|
+
function renderCronRunDetails(lr) {
|
|
24854
|
+
var ok = lr.status === 'ok';
|
|
24855
|
+
var statusColor = ok ? 'var(--green)' : (lr.status === 'error' ? 'var(--red)' : 'var(--yellow)');
|
|
24856
|
+
var statusIcon = ok ? '✓' : (lr.status === 'error' ? '✗' : '⏱');
|
|
24857
|
+
var dur = lr.durationMs != null ? formatDurationMs(lr.durationMs) : '—';
|
|
24858
|
+
var when = lr.finishedAt || lr.startedAt;
|
|
24859
|
+
var whenLabel = when ? new Date(when).toLocaleString() : '—';
|
|
24860
|
+
var html = ''
|
|
24861
|
+
+ '<div style="padding:24px">'
|
|
24862
|
+
+ '<div style="display:flex;align-items:baseline;gap:10px;margin-bottom:14px">'
|
|
24863
|
+
+ '<span style="color:' + statusColor + ';font-size:18px">' + statusIcon + '</span>'
|
|
24864
|
+
+ '<span style="font-size:14px;font-weight:600;color:var(--text-primary);text-transform:capitalize">' + esc(lr.status || 'unknown') + '</span>'
|
|
24865
|
+
+ '<span style="flex:1"></span>'
|
|
24866
|
+
+ '<span style="font-size:12px;color:var(--text-muted)">' + esc(whenLabel) + ' · ' + esc(dur) + (lr.attempt && lr.attempt > 1 ? ' · attempt ' + esc(lr.attempt) : '') + '</span>'
|
|
24867
|
+
+ '</div>';
|
|
24868
|
+
if (lr.goalCheck) {
|
|
24869
|
+
var gc = lr.goalCheck;
|
|
24870
|
+
var gIcon = gc.status === 'pass' ? '🎯' : gc.status === 'fail' ? '✗' : '⚠';
|
|
24871
|
+
var gColor = gc.status === 'pass' ? 'var(--green)' : gc.status === 'fail' ? 'var(--red)' : 'var(--yellow)';
|
|
24872
|
+
var gLabel = gc.status === 'pass' ? 'Goal met' : gc.status === 'fail' ? 'Goal NOT met' : 'Goal evaluation failed';
|
|
24873
|
+
var gReason = gc.evaluatorReason || (Array.isArray(gc.schemaErrors) ? gc.schemaErrors.join('; ') : '');
|
|
24874
|
+
html += '<div style="padding:10px 14px;border-radius:6px;background:rgba(255,255,255,0.04);border-left:3px solid ' + gColor + ';margin-bottom:14px">'
|
|
24875
|
+
+ '<div style="font-size:13px;font-weight:500;color:' + gColor + '">' + gIcon + ' ' + gLabel + '</div>'
|
|
24876
|
+
+ (gReason ? '<div style="font-size:12px;color:var(--text-secondary);margin-top:4px">' + esc(gReason) + '</div>' : '')
|
|
24877
|
+
+ '</div>';
|
|
24878
|
+
}
|
|
24879
|
+
if (lr.error) {
|
|
24880
|
+
html += '<div style="margin-bottom:14px"><div style="font-size:11px;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.04em;margin-bottom:6px">Error</div>'
|
|
24881
|
+
+ '<div style="font-family:\\x27JetBrains Mono\\x27,monospace;font-size:11px;color:var(--red);background:rgba(239,68,68,0.06);border:1px solid rgba(239,68,68,0.2);padding:10px;border-radius:6px;white-space:pre-wrap;word-break:break-word">'
|
|
24882
|
+
+ esc(String(lr.error).slice(0, 2000)) + '</div></div>';
|
|
24883
|
+
}
|
|
24884
|
+
if (lr.outputPreview) {
|
|
24885
|
+
html += '<div style="margin-bottom:14px"><div style="font-size:11px;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.04em;margin-bottom:6px">Output preview</div>'
|
|
24886
|
+
+ '<div style="font-size:12px;color:var(--text-primary);background:var(--bg-secondary);border:1px solid var(--border);padding:10px;border-radius:6px;white-space:pre-wrap;word-break:break-word;max-height:300px;overflow-y:auto">'
|
|
24887
|
+
+ esc(String(lr.outputPreview).slice(0, 4000)) + '</div></div>';
|
|
24888
|
+
}
|
|
24889
|
+
if (Array.isArray(lr.skillsApplied) && lr.skillsApplied.length) {
|
|
24890
|
+
html += '<div style="font-size:11px;color:var(--text-muted);margin-bottom:6px">Skills active: ' + esc(lr.skillsApplied.map(function(s){ return s.name; }).join(', ')) + '</div>';
|
|
24891
|
+
}
|
|
24892
|
+
if (Array.isArray(lr.mcpServersApplied) && lr.mcpServersApplied.length) {
|
|
24893
|
+
html += '<div style="font-size:11px;color:var(--text-muted);margin-bottom:6px">MCP servers: ' + esc(lr.mcpServersApplied.join(', ')) + '</div>';
|
|
24894
|
+
}
|
|
24895
|
+
html += '<div style="margin-top:14px;display:flex;gap:8px"><button class="btn-sm" onclick="openTraceViewer(\\x27' + jsStr(lr.jobName || editingCronJob || '') + '\\x27)" style="font-size:11px">Open trace</button></div>';
|
|
24896
|
+
html += '</div>';
|
|
24897
|
+
return html;
|
|
24898
|
+
}
|
|
24899
|
+
|
|
24900
|
+
// Click handler for the green "Run task once" button. Triggers the existing
|
|
24901
|
+
// /api/cron/run/:job endpoint and switches to the Last run tab so the user
|
|
24902
|
+
// sees the running state. The SSE handler at the bottom of this file picks
|
|
24903
|
+
// up cron_complete and re-renders the pane with the result.
|
|
24904
|
+
async function runCronOnceFromModal() {
|
|
24905
|
+
if (!editingCronJob) {
|
|
24906
|
+
toast('Save the task first, then Run task once.', 'error');
|
|
24907
|
+
return;
|
|
24908
|
+
}
|
|
24909
|
+
if (_cronRunOnceInFlight) {
|
|
24910
|
+
toast('Already running — wait for the current run to finish.', 'info');
|
|
24911
|
+
return;
|
|
24912
|
+
}
|
|
24913
|
+
if (isCronModalDirty()) {
|
|
24914
|
+
if (!confirm('You have unsaved changes. Run the SAVED version (your edits stay in the form)?')) return;
|
|
24915
|
+
}
|
|
24916
|
+
var btn = document.getElementById('cron-run-once-btn');
|
|
24917
|
+
if (btn) { btn.disabled = true; btn.textContent = 'Triggering…'; }
|
|
24918
|
+
_cronRunOnceInFlight = { jobName: editingCronJob, startedAt: Date.now() };
|
|
24919
|
+
// Show the running state and switch the pane immediately.
|
|
24920
|
+
switchCronTab('lastrun');
|
|
24921
|
+
var pane = document.getElementById('cron-lastrun-body');
|
|
24922
|
+
if (pane) pane.innerHTML = renderCronRunningState(_cronRunOnceInFlight.startedAt);
|
|
24923
|
+
// Tick the elapsed counter once a second.
|
|
24924
|
+
if (_cronRunOnceTickerId) clearInterval(_cronRunOnceTickerId);
|
|
24925
|
+
_cronRunOnceTickerId = setInterval(function() {
|
|
24926
|
+
if (!_cronRunOnceInFlight) { clearInterval(_cronRunOnceTickerId); _cronRunOnceTickerId = null; return; }
|
|
24927
|
+
var elapsedEl = document.getElementById('cron-run-once-elapsed');
|
|
24928
|
+
if (elapsedEl) {
|
|
24929
|
+
var s = Math.max(0, Math.round((Date.now() - _cronRunOnceInFlight.startedAt) / 1000));
|
|
24930
|
+
elapsedEl.textContent = s + 's';
|
|
24931
|
+
}
|
|
24932
|
+
}, 1000);
|
|
24933
|
+
try {
|
|
24934
|
+
var r = await apiFetch('/api/cron/run/' + encodeURIComponent(editingCronJob), { method: 'POST' });
|
|
24935
|
+
var d = await r.json();
|
|
24936
|
+
if (!r.ok || d.ok === false) {
|
|
24937
|
+
toast(d.error || 'Run failed to start', 'error');
|
|
24938
|
+
_cronRunOnceInFlight = null;
|
|
24939
|
+
if (_cronRunOnceTickerId) { clearInterval(_cronRunOnceTickerId); _cronRunOnceTickerId = null; }
|
|
24940
|
+
if (pane) pane.innerHTML = '<div style="padding:36px 24px;color:var(--red);text-align:center;font-size:13px">' + esc(d.error || 'Run failed to start') + '</div>';
|
|
24941
|
+
}
|
|
24942
|
+
} catch (e) {
|
|
24943
|
+
toast('Run failed to start: ' + String(e), 'error');
|
|
24944
|
+
_cronRunOnceInFlight = null;
|
|
24945
|
+
if (_cronRunOnceTickerId) { clearInterval(_cronRunOnceTickerId); _cronRunOnceTickerId = null; }
|
|
24946
|
+
} finally {
|
|
24947
|
+
if (btn) { btn.disabled = false; btn.textContent = '▶ Run task once'; }
|
|
24948
|
+
}
|
|
24949
|
+
}
|
|
24950
|
+
|
|
24951
|
+
// Called from the SSE handler when cron_complete fires. The same SSE handler
|
|
24952
|
+
// also schedules refreshCron() which updates cronJobsData with the fresh
|
|
24953
|
+
// lastRun. We just wait a beat for that, then re-render the pane.
|
|
24954
|
+
function handleCronRunOnceComplete(jobName) {
|
|
24955
|
+
if (!_cronRunOnceInFlight || _cronRunOnceInFlight.jobName !== jobName) return;
|
|
24956
|
+
if (_cronRunOnceTickerId) { clearInterval(_cronRunOnceTickerId); _cronRunOnceTickerId = null; }
|
|
24957
|
+
_cronRunOnceInFlight = null;
|
|
24958
|
+
// refreshCron is racing with us; give it ~600ms to land the new entry
|
|
24959
|
+
// into cronJobsData before we read. The SSE handler at line 34927 already
|
|
24960
|
+
// kicks it off when this event arrives.
|
|
24961
|
+
setTimeout(function() {
|
|
24962
|
+
var pane = document.getElementById('cron-lastrun-body');
|
|
24963
|
+
if (!pane) return;
|
|
24964
|
+
var fresh = (Array.isArray(cronJobsData) ? cronJobsData : []).find(function(j) { return j.name === jobName; });
|
|
24965
|
+
var lr = fresh && fresh.lastRun;
|
|
24966
|
+
if (lr) {
|
|
24967
|
+
pane.innerHTML = renderCronRunDetails(lr);
|
|
24968
|
+
toast('Run finished — ' + (lr.status === 'ok' ? 'success' : lr.status), lr.status === 'ok' ? 'success' : 'error');
|
|
24969
|
+
} else {
|
|
24970
|
+
pane.innerHTML = '<div style="padding:36px 24px;color:var(--text-muted);text-align:center;font-size:13px">Run finished but the result is still propagating. Refresh the dashboard to see it.</div>';
|
|
24971
|
+
}
|
|
24972
|
+
}, 600);
|
|
24973
|
+
}
|
|
24974
|
+
|
|
24787
24975
|
// ── Predictable mode: visual card sync + legacy banner ───────────
|
|
24788
24976
|
function onPredictableChange() {
|
|
24789
24977
|
var predEl = document.getElementById('cron-predictable');
|
|
@@ -24899,6 +25087,11 @@ function openCreateCronModal(agentSlug) {
|
|
|
24899
25087
|
// No saved state to preview when creating — disable the Preview tab.
|
|
24900
25088
|
var previewBtn = document.getElementById('cron-tab-btn-preview');
|
|
24901
25089
|
if (previewBtn) previewBtn.setAttribute('disabled', 'disabled');
|
|
25090
|
+
// Last run + Run-task-once button only make sense for saved tasks.
|
|
25091
|
+
var lastRunBtn = document.getElementById('cron-tab-btn-lastrun');
|
|
25092
|
+
if (lastRunBtn) lastRunBtn.setAttribute('disabled', 'disabled');
|
|
25093
|
+
var runOnceBtn = document.getElementById('cron-run-once-btn');
|
|
25094
|
+
if (runOnceBtn) runOnceBtn.style.display = 'none';
|
|
24902
25095
|
var host = document.getElementById('cron-legacy-banner-host');
|
|
24903
25096
|
if (host) host.innerHTML = '';
|
|
24904
25097
|
// Reset the "Use a cron expression" link in case it was hidden last time.
|
|
@@ -24990,9 +25183,18 @@ function openEditCronModal(jobName) {
|
|
|
24990
25183
|
renderTagsPickerChips();
|
|
24991
25184
|
_pendingAttachments = [];
|
|
24992
25185
|
loadCronAttachments(jobName);
|
|
24993
|
-
// Existing job has saved state, enable Preview
|
|
25186
|
+
// Existing job has saved state, enable Preview + Last run tabs.
|
|
24994
25187
|
var previewBtn = document.getElementById('cron-tab-btn-preview');
|
|
24995
25188
|
if (previewBtn) previewBtn.removeAttribute('disabled');
|
|
25189
|
+
var lastRunBtnEdit = document.getElementById('cron-tab-btn-lastrun');
|
|
25190
|
+
if (lastRunBtnEdit) lastRunBtnEdit.removeAttribute('disabled');
|
|
25191
|
+
// Show "Run task once" only for saved tasks.
|
|
25192
|
+
var runOnceBtnEdit = document.getElementById('cron-run-once-btn');
|
|
25193
|
+
if (runOnceBtnEdit) runOnceBtnEdit.style.display = '';
|
|
25194
|
+
// Render the most recent run from the loaded job into the Last run tab so
|
|
25195
|
+
// the user sees something the moment they switch to it (rather than a
|
|
25196
|
+
// dead empty pane). The pane updates live when Run task once fires.
|
|
25197
|
+
renderCronLastRunPane(job);
|
|
24996
25198
|
switchCronTab('configure');
|
|
24997
25199
|
document.getElementById('cron-modal').classList.add('show');
|
|
24998
25200
|
setTimeout(captureCronModalSnapshot, 0);
|
|
@@ -25200,6 +25402,10 @@ function closeCronModal(force) {
|
|
|
25200
25402
|
editingCronJob = null;
|
|
25201
25403
|
_cronPreviewLoadedFor = null;
|
|
25202
25404
|
_cronModalSnapshot = null;
|
|
25405
|
+
// Clear any pending Run-task-once watch so SSE events for a different job
|
|
25406
|
+
// don't accidentally re-render the (now closed) Last run pane.
|
|
25407
|
+
if (_cronRunOnceTickerId) { clearInterval(_cronRunOnceTickerId); _cronRunOnceTickerId = null; }
|
|
25408
|
+
_cronRunOnceInFlight = null;
|
|
25203
25409
|
var attachList = document.getElementById('cron-attachments-list');
|
|
25204
25410
|
if (attachList) attachList.innerHTML = '';
|
|
25205
25411
|
var bannerHost = document.getElementById('cron-legacy-banner-host');
|
|
@@ -34891,6 +35097,11 @@ try {
|
|
|
34891
35097
|
refreshActivity();
|
|
34892
35098
|
if (currentPage === 'build') refreshCron();
|
|
34893
35099
|
refreshTeamNav();
|
|
35100
|
+
// PRD Phase 1.2: if the user just clicked "Run task once" in the
|
|
35101
|
+
// modal, re-render the Last run pane with the fresh result.
|
|
35102
|
+
if (evt.type === 'cron_complete' && evt.data && evt.data.job && typeof handleCronRunOnceComplete === 'function') {
|
|
35103
|
+
try { handleCronRunOnceComplete(evt.data.job); } catch (err) { /* non-fatal */ }
|
|
35104
|
+
}
|
|
34894
35105
|
}
|
|
34895
35106
|
// A delete on one tab should drop the card from every open dashboard
|
|
34896
35107
|
// without waiting for the next poll. cron_toggled is similar but lighter.
|