Skip to content

feat(console): show unread indicator when session task finishes - #7275

Merged
lalaliat merged 2 commits into
agentscope-ai:mainfrom
lalaliat:la/dev/task_finish
Aug 25, 2026
Merged

feat(console): show unread indicator when session task finishes#7275
lalaliat merged 2 commits into
agentscope-ai:mainfrom
lalaliat:la/dev/task_finish

Conversation

@lalaliat

Copy link
Copy Markdown
Collaborator

Description

Adds a completion attention indicator for chat sessions.

When a task finishes:

  • The indicator becomes gray if the user is viewing that session.
  • The indicator becomes a solid teal dot if the user is viewing another session.
  • Opening the completed session marks the result as viewed.
  • Running tasks continue to use the animated indicator.

The backend now records each session's latest task completion time. The Console stores the latest viewed completion time per agent and session. Existing sessions are treated as viewed during initial migration to avoid marking old history as unread.

image

Related Issue: #7263

Security Considerations: No security-sensitive behavior, authentication, or configuration changes.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Refactoring

Component(s) Affected

  • Core / Backend (app, agents, config, providers, utils, local_models)
  • Console (frontend web UI)
  • Channels (DingTalk, Lark, QQ, Discord, iMessage, etc.)
  • Skills
  • CLI
  • Documentation (website)
  • Tests
  • CI/CD
  • Scripts / Deploy

Checklist

  • I ran pre-commit run --all-files locally and it passes
  • If pre-commit auto-fixed files, I committed those changes and reran checks
  • I ran tests locally (pytest or as relevant) and they pass
  • Documentation updated (if needed)
  • Ready for review

For Channel Changes (DingTalk, Lark, QQ, Console, etc.)

  • I ran ./scripts/check-channels.sh (or ./scripts/check-channels.sh --changed) and it passes
  • Contract test exists in tests/contract/channels/test_<channel>_contract.py (REQUIRED)
  • Contract test implements create_instance() with proper channel initialization
  • All 19 contract verification points pass (see tests/contract/channels/__init__.py)
  • Optional: Unit tests in tests/unit/channels/test_<channel>.py for complex internal logic

Not applicable: this change does not modify a channel implementation or channel contract.

Testing

The following scenarios are covered:

  1. A running session displays an animated indicator.
  2. A completed session displays a solid indicator when its result has not been viewed.
  3. Opening the session marks the latest result as viewed.
  4. A session completing while currently visible remains in the viewed state.
  5. Read markers are isolated by agent and backend session ID.
  6. Existing sessions are initialized as viewed.
  7. Task completion times are persisted by the backend.
  8. Older completion timestamps cannot overwrite newer timestamps.

Evidence

@github-actions

Copy link
Copy Markdown

Welcome to QwenPaw! 🐾

Hi @lalaliat, this is your 72nd Pull Request.

🙌 Join Developer Community

Thanks so much for your contribution! We'd love to invite you to join the official QwenPaw developer group! You can find the Discord and DingTalk group links under the "Developer Community" section on our docs page:
https://qwenpaw.agentscope.io/docs/community

We truly appreciate your enthusiasm—and look forward to your future contributions! 😊

We'll review your PR soon.

@lalaliat
lalaliat deployed to maintainer-approved August 25, 2026 07:42 — with GitHub Actions Active
@github-actions

Copy link
Copy Markdown

✅ QwenPaw AI Review: Passed AI Review — Awaiting Human Review

1. Overview

Item Details
PR Number #7275
Author @lalaliat
Changes +409 / -3 lines across 22 files (Python backend, TypeScript frontend, tests, locales)
Merge Target main
Related Issue #7263

2. Background

This PR adds a "completion attention indicator" to the Console session list. When an agent task finishes, sessions the user isn't currently viewing show a teal dot to signal a new result. Opening the session clears the indicator. The backend persists last_finished_at on each ChatSpec, and the frontend stores per-agent/session read markers in localStorage. Existing sessions are baseline-initialized as "seen" so the feature doesn't mark old history unread.

3. Core Changes

  • Backend data model (src/qwenpaw/app/chats/models.py): Added last_finished_at: Optional[datetime] to ChatSpec.
  • Backend completion callback (src/qwenpaw/app/chats/manager.py): New mark_chat_finished() method that persists the newest completion timestamp under the manager lock, with a guard against older timestamps overwriting newer ones.
  • TaskTracker hook (src/qwenpaw/app/task_tracker.py): Added optional on_finished callback parameter to attach_or_start(). The callback is invoked in the _producer's finally block, before cleanup and sentinel broadcast.
  • Router wiring (src/qwenpaw/app/routers/console.py, src/qwenpaw/app/channels/base.py): Both console and base channel now pass workspace.chat_manager.mark_chat_finished as the on_finished callback.
  • Frontend state store (console/src/stores/sessionAttentionStore.ts): New Zustand store with persist middleware tracking seenFinishedAt per agentId:realId key. initializeSessions baselines existing sessions; markSeen updates when the user views a session.
  • Frontend hook (console/src/hooks/useSessionAttention.ts): useSessionAttention orchestrates initialization, visibility-change-based marking, and computes a ReadonlySet<string> of unseen session IDs.
  • UI rendering (SessionItem/index.tsx, sessionItem.module.less): New unseenResult prop, new unseenDot / statusDotUnseen CSS classes (teal glow dot), and priority logic (running > unseen > idle).
  • Integration points (SidebarSessionList.tsx, ChatSessionDrawer/index.tsx): Both wire useSessionAttention and pass unseenSessionIds.has(session.id) to SessionItem.
  • Type plumbing: lastFinishedAt added to all ExtendedSession/ExtendedChatSession/ChatSpec interfaces and to the session equality checks (sessionsEqual, isSessionListEqual).
  • i18n: chat.statusUnseenResult added to en.json and zh.json.
  • Tests: Unit tests for mark_chat_finished (backend), sessionAttentionStore (frontend), and attach_or_start with on_finished (task tracker).

4. Strengths

  1. Clean callback architecture — The on_finished hook in TaskTracker is invoked in the finally block before cleanup, allowing the ChatManager to persist state while the run is still considered "active" by the tracker. This avoids a race where a reconnect subscriber arrives after the run is removed but before the timestamp is saved.
  2. Smart baseline initializationinitializeSessions sets seenFinishedAt[key] = session.lastFinishedAt for every known session on first encounter, so an upgrade doesn't flood the UI with unread dots for old history.
  3. Correct key derivationsessionAttentionKey uses realId || id, ensuring read markers survive local ID remapping (timestamp → UUID).
  4. Monotonic timestamp guardmark_chat_finished rejects older finished_at values (existing.last_finished_at >= finished_at), preventing a slow callback from overwriting a newer completion.
  5. Good test coverage — All three new logical units (store, hook logic, backend method, tracker callback) have targeted tests, including the edge case of stale timestamps.

5. Issues and Suggestions

High

None.

Medium

None.

Low

1. markCurrentSeen re-registered on every polling cycle

  • File: console/src/hooks/useSessionAttention.ts:33-38
  • Issue: markCurrentSeen has sessions in its dependency array. Since sessions are polled every 3 seconds, the callback is recreated and the visibilitychange listener is removed/re-added on every render. While markSeen has an early-return guard (so no actual state churn), this is unnecessary DOM churn.
  • Suggestion: Use a ref for sessions in the callback, or extract the session lookup into a stable function keyed only by [agentId, currentSessionId]:
    const sessionsRef = useRef(sessions);
    sessionsRef.current = sessions;
    
    const markCurrentSeen = useCallback(() => {
      if (!currentSessionId || document.visibilityState !== "visible") return;
      const current = sessionsRef.current.find((s) =>
        matchesCurrentSession(s, currentSessionId),
      );
      if (current) markSeen(agentId, current);
    }, [agentId, currentSessionId, markSeen]);

2. Brief window where navigated session may not be in sessions array

  • File: console/src/hooks/useSessionAttention.ts:33-38
  • Issue: When the user clicks a session, currentSessionId updates immediately via URL change, but the sessions array may not have been updated by polling yet. The sessions.find() call would not find the session, and markSeen would be skipped. The teal dot could briefly flash on the just-navigated-to session until the next poll cycle.
  • Suggestion: Consider also marking the session as seen on navigation (e.g., in the session click handler) rather than relying solely on the polling-driven sessions array. This is cosmetic and self-healing within ~3 seconds, so deferrable.

3. mark_chat_finished acquires the ChatManager lock during task teardown

  • File: src/qwenpaw/app/task_tracker.py:312-321 (callback invocation) / src/qwenpaw/app/chats/manager.py:404-424
  • Note: The on_finished callback is awaited in the _producer's finally block, which acquires ChatManager._lock and performs file I/O. During this time, the task is still in _runs and considered "running." This is by design (confirmed by test_attach_or_start_reports_completion_before_becoming_idle), but it means any get_status() call during the callback window still returns "running". Consumers (e.g., the sidebar polling) may see a brief period where the task is idle from their perspective but the tracker reports it as running. Not a bug — just worth documenting in the docstring.

5.5 Cross-file Impact Analysis

Changed Symbol Other Usages Checked Impact
attach_or_start(on_finished=...) Searched codebase — only two callers: base.py and console.py, both updated in this PR. Parameter is optional (default None), so existing callers outside the PR are unaffected. No external impact
ChatSpec.last_finished_at Added to all frontend ExtendedSession/ExtendedChatSession interfaces and the sessionsEqual/isSessionListEqual comparison functions — all updated consistently. Backend model is the single source of truth. Self-contained within PR
sessionAttentionKey / hasUnseenCompletion Only used internally within sessionAttentionStore.ts and useSessionAttention.ts. Self-contained within PR
useSessionAttention hook Consumed by SidebarSessionList.tsx and ChatSessionDrawer/index.tsx — both updated in this PR. Self-contained within PR
mark_chat_finished New method on ChatManager. Only called via the on_finished callback from TaskTracker. Self-contained within PR

No external call sites outside the PR are affected. The feature is fully self-contained.

6. Summary

This is a well-engineered feature with clean separation between backend persistence, state management, and UI rendering. The callback-driven on_finished pattern in TaskTracker is a good design choice, and the baseline initialization logic correctly avoids false-positives for existing sessions. Two minor optimization opportunities exist in the hook (dependency array and navigation-to-session race), but neither is a blocker.

  • 0 items must be addressed before merge.
  • 2 items can be followed up later (Low-severity: hook dependency stability and brief navigation flash).

Auto-generated by QwenPaw Review Agent | Triggered by maintainer approval

@lalaliat
lalaliat requested a deployment to ai-review-approved August 25, 2026 08:09 — with GitHub Actions Waiting
@lalaliat
lalaliat deployed to maintainer-approved August 25, 2026 08:09 — with GitHub Actions Active

@zhaozhuang521 zhaozhuang521 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@lalaliat
lalaliat merged commit 0dd1844 into agentscope-ai:main Aug 25, 2026
32 of 33 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in QwenPaw Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Feature]: 任务完成提醒,任务完成后底栏的活动标签显示橙色

2 participants