Fix task activity scrolling and background spam

This commit is contained in:
pewdiepie-archdaemon
2026-06-30 08:16:19 +00:00
parent a72ec0c116
commit 838cacf132
6 changed files with 57 additions and 9 deletions
+34
View File
@@ -687,6 +687,12 @@ class TaskScheduler:
db = SessionLocal()
try:
now = _utcnow()
foreground_active = False
try:
from src.interactive_gate import has_foreground_activity
foreground_active = has_foreground_activity()
except Exception:
foreground_active = False
async with self._executing_lock:
# Snapshot under the lock so we don't race with mid-iteration adds.
executing_snapshot = set(self._executing)
@@ -700,8 +706,13 @@ class TaskScheduler:
for task in due:
if task.id in self._executing:
continue
if foreground_active:
task.next_run = now + timedelta(minutes=15)
continue
self._executing.add(task.id)
to_dispatch.append(task.id)
if foreground_active and due:
db.commit()
for task_id in to_dispatch:
asyncio.create_task(self._execute_task(task_id))
finally:
@@ -754,6 +765,7 @@ class TaskScheduler:
# If cancellation happens while queued behind the semaphore,
# _execute_task_locked never runs and cannot update the Activity row.
self._mark_run_aborted(task_id, run_id)
self._defer_immediately_due_task(task_id, delay=timedelta(minutes=15))
raise
finally:
handle = self._task_handles.get(task_id)
@@ -763,6 +775,28 @@ class TaskScheduler:
async with self._executing_lock:
self._executing.discard(task_id)
def _defer_immediately_due_task(self, task_id: str, *, delay: timedelta):
"""A queued task can be cancelled before _execute_task_locked gets a DB
handle. If its next_run stays in the past, the scheduler dispatches it
again on the next tick and spams aborted Activity rows."""
try:
from core.database import SessionLocal, ScheduledTask
db = SessionLocal()
try:
task = db.query(ScheduledTask).filter(ScheduledTask.id == task_id).first()
if (
task
and task.status == "active"
and task.next_run is not None
and task.next_run <= _utcnow()
):
task.next_run = _utcnow() + delay
db.commit()
finally:
db.close()
except Exception:
logger.debug("Failed to defer cancelled queued task %s", task_id, exc_info=True)
async def _execute_task_locked(
self,
task_id: str,
+1 -1
View File
@@ -22,7 +22,7 @@ import memoryModule from './js/memory.js';
import voiceRecorderModule from './js/voiceRecorder.js';
import censorModule from './js/censor.js';
import galleryModule from './js/gallery.js';
import tasksModule from './js/tasks.js';
import tasksModule from './js/tasks.js?v=20260630tasksactivity';
import calendarModule from './js/calendar.js';
import notesModule from './js/notes.js';
import adminModule from './js/admin.js';
+3 -3
View File
@@ -219,8 +219,8 @@
}, { once: true });
})();
</script>
<link rel="stylesheet" href="/static/style.css?v=20260630researchthumb">
<link rel="modulepreload" href="/static/app.js?v=20260630researchthumb">
<link rel="stylesheet" href="/static/style.css?v=20260630tasksactivity">
<link rel="modulepreload" href="/static/app.js?v=20260630tasksactivity">
<link rel="modulepreload" href="/static/js/chat.js">
<link rel="modulepreload" href="/static/js/ui.js">
<link rel="modulepreload" href="/static/js/sessions.js">
@@ -2512,7 +2512,7 @@
<script type="module" src="/static/js/settings.js"></script>
<script type="module" src="/static/js/admin.js"></script>
<script type="module" src="/static/js/assistant.js"></script>
<script type="module" src="/static/app.js?v=20260630researchthumb"></script> <!-- app.js must be LAST -->
<script type="module" src="/static/app.js?v=20260630tasksactivity"></script> <!-- app.js must be LAST -->
<script type="module" src="/static/js/init.js"></script>
<script type="module" src="/static/js/a11y.js"></script>
<script nonce="{{CSP_NONCE}}">if('serviceWorker' in navigator){navigator.serviceWorker.register('/static/sw.js').catch(()=>{});}</script>
+9 -3
View File
@@ -1948,7 +1948,7 @@ async function _renderActivityView() {
const body = modal?.querySelector('.modal-body');
if (!body) return;
body.innerHTML = `
<div class="admin-card" style="flex:1;display:flex;flex-direction:column;overflow:hidden;">
<div class="admin-card tasks-activity-card" style="flex:1;display:flex;flex-direction:column;overflow:hidden;min-height:0;">
<div style="display:flex;align-items:baseline;gap:8px;margin-bottom:2px;">
<h2 style="margin:0;padding:0;line-height:1;">Activity</h2>
<button class="memory-toolbar-btn" id="tasks-activity-refresh" title="Refresh" style="margin-left:auto;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;"><path d="M1 4v6h6"/><path d="M23 20v-6h-6"/><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"/></svg></button>
@@ -1958,7 +1958,7 @@ async function _renderActivityView() {
<input type="text" id="tasks-activity-search" placeholder="Filter activity…" class="memory-search-input" style="flex:1;" />
</div>
<div class="tasks-activity-filters" id="tasks-activity-chips" style="display:flex;gap:5px;margin-bottom:8px;flex-wrap:wrap;"></div>
<div id="tasks-activity-list" class="memory-list" style="flex:1;overflow:auto;font-size:13px;"></div>
<div id="tasks-activity-list" class="memory-list tasks-activity-list" style="flex:1;overflow:auto;font-size:13px;min-height:0;"></div>
</div>
`;
@@ -2123,6 +2123,7 @@ let _activityHasMore = false;
function _stackActivityEntries(entries) {
const out = [];
const byKey = new Map();
const maxStack = 8;
const hourBucket = (ts) => {
const d = ts ? new Date(ts) : null;
if (!d || Number.isNaN(d.getTime())) return '';
@@ -2150,7 +2151,12 @@ function _stackActivityEntries(entries) {
/^Email\b/i.test(entry.taskName || '') ? hourBucket(entry.ts) : '',
].join('\u0001');
const existing = byKey.get(key);
if (existing && entry.status !== 'running' && entry.status !== 'queued') {
if (
existing
&& entry.status !== 'running'
&& entry.status !== 'queued'
&& (existing.repeatCount || 1) < maxStack
) {
existing.repeatCount = (existing.repeatCount || 1) + 1;
continue;
}
+9 -1
View File
@@ -11063,7 +11063,7 @@ textarea.memory-add-input {
text-transform: uppercase;
letter-spacing: 0.3px;
padding: 1px 6px;
border-radius: 3px;
border-radius: 999px;
flex-shrink: 0;
cursor: pointer;
border: 1px solid transparent;
@@ -23305,6 +23305,14 @@ body:not(.welcome-ready) #welcome-screen {
top: 8px;
}
.tasks-modal-content { max-width: 600px; width: min(600px, 92vw); background: var(--bg); font-size: 12px; }
.tasks-activity-card,
.tasks-activity-list {
min-height: 0;
}
.tasks-activity-list {
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
/* Tasks tabs reuse the .memory-tab look. The Brain window's tab bar is
full-bleed (its underline spans the whole modal width). The Tasks bar is a
+1 -1
View File
@@ -7,7 +7,7 @@
// - Other static assets (images/fonts/libs): cache-first with bg refresh.
// - API / non-GET: never cached.
// Bump CACHE_NAME whenever the precache list or SW logic changes.
const CACHE_NAME = 'odysseus-v328';
const CACHE_NAME = 'odysseus-v329';
// Core shell precached on install so repeat opens are instant without any
// network wait. Keep this list in sync with the <script type="module"> tags