Skip to content

Concept: desktop task activity and ready/unread counts in the Cinnamon panel #39

Description

@ClaudiuSchuster

User problem

The ChatGPT desktop pet exposes current task activity and ready/unread output.
The Cinnamon applet already provides an always-visible place for usage limits,
but it cannot tell the user how many tasks are working or have new results.
Show these counts in the applet using the desktop app's actual state, without
requiring the pet to remain visible or marking any task as read.

This issue is an implementation handoff and concept, not an implemented feature.
The critical unknown is a reliable, read-only connection to the running desktop
app. Finding status fields inside its bundle does not prove that external
applications can access them through a supported transport.

Desired outcome

  • Running: number of distinct tasks currently working.
  • Ready / unread: number of distinct tasks with unread completed output.
  • Counts update after starting, completing, reading, archiving or restoring a
    task, and recover correctly after app/applet restarts.
  • Reading status in the applet never changes the desktop app's unread state.
  • Optional panel indicators work on horizontal and vertical panels. Existing
    quota geometry, Spark collapse rules and quota refresh behavior are preserved.
  • Unsupported, disconnected or incomplete data is distinguishable from zero.

Baseline and evidence

Research date: 2026-09-05. Repository baseline: v0.3.10, commit
0f4830bc4d1d98c90b3b93086bbd99fbf4b903aa.
Desktop app inspected: ChatGPT Linux 26.901.31953.

Confirmed in the installed desktop bundle

Read-only inspection of the packaged resources/app.asar found:

Bundle member at the inspected version Relevant evidence
webview/assets/avatar-overlay-native-page-fcebab641c9f.js Pet task status mapping includes running, review, waiting, failed, idle. The review label is “Ready”, described as unread completed output.
Same member Local records use hasUnreadTurn; cloud records use has_unread_turn. Running predicates also consider runtime/turn state.
Same member Local records can carry hostId, localConversationId, turnKey, source, showInNotificationTray, and ordering timestamps. Cloud records have their own identity and state mapping.
webview/assets/app-initial-c8dbea294abe.js Internal broadcast thread-read-state-changed; receiving handler checks params.hostId and calls setConversationUnreadState(params.conversationId, params.hasUnreadTurn, false).

These hashed filenames and minified identifiers are discovery pointers only.
Do not make the shipped feature depend on them. Rediscover the installed bundle
and search stable semantic strings after upgrades. Do not patch the bundle.

Observed local status precedence is broadly:

  1. Approval/input/plan interaction requiring attention → waiting.
  2. Relevant system/turn failure → failed.
  3. Active runtime, resuming task, or in-progress turn → running.
  4. Otherwise, hasUnreadTurnreview.
  5. Otherwise → idle.

The waiting predicate includes application helpers that were not fully traced;
this list is not a complete independent implementation specification.
For cloud records, the inspected mapping treats archived tasks as idle,
failed/cancelled turns as failed, pending/in-progress turns as running, and
otherwise uses has_unread_turn for review.

Important unresolved detail: the pet also filters, groups and expires
notification presentations. A number on its activity badge is not yet proven
to equal the count of all unread tasks. Trace the actual collection feeding the
badge, including hidden items, local/cloud scope, grouping and expiry. Separate
the durable task-status model from a transient notification-stack length.

Confirmed in official documentation

App Server documentation describes:

  • thread/list, including returned runtime status;
  • thread/loaded/list, listing tasks loaded in that server's memory;
  • thread/status/changed, including active flags such as waiting for approval;
  • thread/read, reading a stored task without subscribing to it.

The inspected documentation did not expose the desktop app's unread/seen-state
API. That is a finding from this investigation, not a guarantee that no such
interface exists in a later version.

Why the current usage helper is insufficient

chatgpt_usage.py::_run_app_server_request() starts a new process with
app-server --listen stdio://, initializes it, performs one request, and stops
it. fetch_rate_limits() currently requests account/rateLimits/read.

Do not assume that thread/loaded/list or runtime statuses from this separate
process represent the tasks running in the desktop application's own server.
Likewise, completed logs alone cannot establish whether the user has read a
result. A terminal event, modification time or SQLite row is not by itself an
authoritative desktop unread flag.

Recommended UX

Settings and default

  • Add Show task activity in panel, initially off to preserve existing
    panel layout for current users. This is independent of model-limit settings.
  • When enabled and a complete fresh snapshot is available, show only nonzero
    categories. Hide the activity group when both counts are zero.
  • Add a compact popup summary when supported, including a truthful empty state.
    Keep unavailable/status diagnostics visible there when the feature is enabled.
  • Use user-facing “Running” and “Ready · unread”; avoid exposing internal
    review, transport names, database names or IPC details in normal UI.

Horizontal panel

Append a small activity group after the existing quota blocks, with a
deliberate separator/gap. Conceptual layout:

[existing quota indicators]  ·  ◌ 1  ● 2
                                  │    └ ready / unread
                                  └ running

The glyphs are illustrative. Use suitable symbolic vector icons, not emoji or
new raster assets. Prefer a static activity icon and a distinguishable unread
dot/badge; no continuous animation is required. A subtle blue unread accent is
reasonable, but text/icon shape must also convey meaning in monochrome mode.
Use stable/tabular count widths where possible, and avoid moving quota blocks
when only a number changes. Category appearance/disappearance may change the
activity group's footprint, but must not resize existing quota widgets.

Vertical panel

Append compact activity cells below the existing quota cells. Reuse the current
icon-above-value convention so it remains readable on a 40 px panel, rather
than squeezing two counters into one row. If both categories are present, stack
them in the same order: running, then ready/unread. Never widen the panel.

Display 99+ for large counts; provide the exact total in the tooltip. Test the
three-character cap in the narrow layout rather than shrinking other applet
text to accommodate it.

Popup and interaction

  • Example summary: Tasks — 1 running · 2 ready/unread.
  • Reuse existing heading, icon and spacing conventions. Put the optional
    summary after Recent consumption and before credits/actions; avoid adding
    another dashboard-sized section or changing quota/chart insets.
  • Hover/accessibility text gives explicit counts and source availability.
  • In the initial version, clicking an activity indicator follows the applet's
    normal popup behavior. Hover, popup opening and status refresh must not mark
    tasks read. Opening individual tasks is a separate later interaction decision.
  • Preserve waiting and failed categories in the data model. Exclude them
    from running/ready rather than misclassifying them. The popup may show a
    compact “Needs input” / “Blocked” count when nonzero; avoid adding two more
    default panel badges before visual review.

Proposed technical design

1. Prove the source before building the panel

Implement a small read-only diagnostic prototype first. Prefer, in order:

  1. A supported app-owned status interface that exposes both current task state
    and the desktop read state, or an existing authenticated local bridge with
    documented external access.
  2. A narrowly scoped local adapter if a stable, legitimately accessible
    application interface can be verified. Label an internal interface as such
    and isolate version-specific logic from the applet.
  3. Persisted metadata only if schema and live-transition tests prove identical
    semantics; use read-only access, handle locking/WAL correctly, and establish
    whether an in-flight runtime status is actually persisted.

The internal thread-read-state-changed event is a lead, not an externally
callable endpoint. No socket URL, D-Bus service or supported subscribe method
has been verified yet. Do not invent one from the event name.

Do not require remote debugging, renderer injection, copying authentication
secrets, modifying the desktop app, or resuming tasks merely to inspect them.
If exact authoritative counts are unavailable, stop the provider implementation
and document that blocker. Do not substitute heuristic counts while labeling
them as the desktop app's live counts.

2. Define counting and identity precisely

  • Count tasks, not messages, turns, tool calls or processes.
  • Use a source-qualified stable identity, such as (kind, hostId, taskId).
  • Reconcile duplicate sidebar, pinned, project and pet representations.
  • Never add hidden subagents to a count if their parent is already the visible
    task represented by the desktop UI. Prove parent/child filtering against the
    app rather than relying on a title or path heuristic.
  • Define local, connected-host and cloud coverage explicitly. Prefer parity
    with the desktop pet's underlying task scope. If only a subset is supported,
    label it (for example, “Local tasks”) and report incomplete coverage.
  • A running task with an older unread result counts as running in the proposed
    mutually exclusive status display; reconcile this with the actual pet mapping
    during the source proof. Reading, archiving, failure and cancellation must
    have explicit transition fixtures.

3. Keep a separate activity snapshot

Suggested helper result, independent of rate-limit history:

{
  "schemaVersion": 1,
  "availability": "available",
  "observedAt": 1788616800,
  "generation": "provider-instance-1",
  "sequence": 42,
  "coverage": {
    "scope": "desktop-app",
    "complete": true
  },
  "counts": {
    "running": 1,
    "readyUnread": 2,
    "waiting": 0,
    "failed": 0
  }
}

This is a proposed applet contract, not an existing OpenAI API payload.
Allow unsupported, disconnected, stale and error availability states;
use counts: null when unknown. Zero is valid only for a fresh, complete
snapshot. A partial result must never be presented as a complete total.

Prefer aggregate counts across the helper/applet boundary; task titles, prompts,
paths and content are unnecessary. If task identifiers are temporarily needed
for deduplication, keep them in memory and out of public diagnostics/fixtures.

Validate count types/ranges, schema version, payload size and freshness. Reject
older or out-of-order snapshots within a provider generation; reset ordering
on a new generation. Reconcile a full snapshot after reconnecting, then apply
events, so reconnects neither duplicate nor lose completion/read transitions.

4. Refresh, failure handling and resource lifecycle

  • Prefer event-driven updates with initial/reconnect reconciliation. If only
    local polling is available, start with a 3–5 second interval and a bounded
    timeout; verify responsiveness and overhead before fixing the interval.
  • Target visible updates within 5 seconds in a healthy session. Target a
    stale indication within 15 seconds after updates/heartbeats stop. Use
    monotonic time for local freshness calculations and document final thresholds.
  • Keep this separate from the existing minute-based usage-refresh timer. Do
    not increase account-rate-limit requests or start a new app-server every few
    seconds merely to check activity.
  • No synchronous I/O on Cinnamon's main thread. Bound retries and apply backoff
    after disconnects; avoid overlapping polls or accumulating subprocesses.
  • On app exit, do not retain apparently live counts. Use a neutral unavailable
    indicator while enabled, with the explanation in the tooltip/popup.
  • On applet disable/removal, stop activity timers/subscriptions, cancel pending
    I/O and terminate only provider processes owned by this applet.
  • Activity failures must not destroy a healthy quota snapshot or overwrite
    quota history; keep source errors scoped to the feature.

Repository entry points

File / method Expected role
applet.js::_rebuildPanel() Append the optional activity group after existing quota actors; currently clears/rebuilds _root. Consider targeted count updates to avoid needless full rebuilds.
applet.js::_createWindowActor() Existing panel sizing conventions to preserve.
applet.js::_rebuildMenu() / _addSectionHeading() Compact summary with existing typography and layout.
applet.js::_restartTimer() Existing account refresh: keep separate from activity updates.
applet.js::on_applet_removed_from_panel() Clean up the independent activity lifecycle.
chatgpt_usage.py::_run_app_server_request() Existing one-shot usage transport; not proof of shared desktop runtime access.
usage-format.js Pure formatting/count-display helpers, if appropriate; keep transport parsing separate.
settings-schema.json Independent feature toggle and readable settings text.
install.sh / uninstall.sh Update packaging symmetrically if a new helper/module is introduced.
Makefile, tests/, .github/workflows/check.yml Wire meaningful unit/behavior tests into the repository's existing checks.
README.md, CHANGELOG.md, metadata.json Document semantics, support boundaries, screenshots and eventual version.

The current deployment audit expects 10 managed regular files. If adding a
packaged file, update the corresponding deployment manifest/audit and expected
count rather than preserving an obsolete “10/10” assertion. Do not use symlinks
for the normal applet installation.

Implementation sequence and decision gates

  1. Read current repository instructions, establish a dedicated session worktree,
    and recheck the latest release and installed desktop version.
  2. Reproduce the pet's status/count mapping and locate the authoritative runtime
    and read-state source. Record transport, coverage, authentication boundary,
    disconnect behavior and compatibility assumptions.
  3. Produce a read-only diagnostic prototype and compare it with the desktop/pet
    during controlled transitions. If exact access cannot be established, report
    findings and remaining choices here before proceeding with UI implementation.
  4. Implement normalization, deduplication, freshness/error handling and fixtures.
  5. Add the opt-in panel cells and compact popup summary. Present horizontal and
    vertical visual variants before selecting final spacing.
  6. Run isolated geometry/screenshot regressions, then a targeted normal
    installation/reload for live desktop parity review.
  7. Update documentation and packaging, run local/remote checks, and use the
    repository's protected PR/release workflow when separately authorized.

Acceptance criteria

  • The issue's unverified source assumptions have been resolved in a written
    provider note; no invented transport or unsupported global-state assumption.
  • Running and ready/unread counts agree with the defined desktop task scope;
    any difference from the visible pet badge is explained and intentional.
  • Verified transitions: start → running; completion → ready/unread; reading
    in the desktop app → cleared; a second unread result; failure; cancellation;
    input/approval waiting; archive/unarchive; reconnect; app/applet restart.
  • Two simultaneous tasks, duplicate representations, parent/subagent cases,
    disconnected hosts, cloud/local overlap and pagination are covered.
  • Polling, tooltip display and opening the applet do not mark tasks read,
    resume them, trigger a model run or change account state.
  • Unknown/stale/partial data is never displayed as a confident zero.
  • Feature disabled: baseline panel/popup behavior is preserved. Feature
    enabled: horizontal and 40 px vertical layouts are readable at 0, 1, 9, 10,
    99 and 100+ counts without resizing existing quota widgets.
  • Spark unused/active states, Codex-only and conditional four-ring layouts
    retain their existing ring sizes, insets, chart alignment and spacing.
  • Keyboard/accessibility labels and monochrome/reduced-motion behavior work.
  • Local provider failures do not block Cinnamon or interrupt quota updates;
    no leaked timers, subscriptions or subprocesses after removal/reloads.
  • make verify, repository-pinned Super-Linter and required remote checks pass.
  • Public captures contain fictional data only; no credentials, user titles,
    private paths, unrelated windows or live account details.
  • README and provider documentation make support/coverage limits explicit.

Visual and live verification plan

Use the cinnamon-isolated-capture workflow for deterministic fake activity
snapshots and screenshot comparisons. It must use private X11, D-Bus, HOME/XDG
state and a UUID-named copied applet with normal production rendering. Preserve
the visible panel/app-icon anchor in public captures. Do not move or hide the
user's real windows or pointer to produce documentation images.

Capture the released baseline and candidate with the same theme, fixture data,
font settings and geometry. Record actor positions/sizes and compare unaffected
regions, explicitly allowing only the new activity group/summary footprint.
Include zero, running-only, ready-only, mixed, unknown/stale and large-count
states on both orientations, plus the existing Spark/quota combinations.

Synthetic captures prove presentation, not access to live desktop state.
Separately verify the real provider with harmless disposable tasks and normal
user read/unread transitions. Do not manufacture parity by modifying task flags.
Install through the normal copy-based installer, audit managed files, use one
targeted ReloadXlet, verify the same Cinnamon PID and healthy applet, and stop
on the first reload failure rather than restarting the desktop session.

Scope boundaries

No automatic task opening, marking-read action, task control, notification
spam, new cloud credentials, screenshot scraping, desktop bundle patches or
publication of real task metadata. No implementation, deployment or release is
performed merely by creating this concept issue.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions