bg-job-toasts

Background job toast notification system for the OpenClaw Control UI. Bottom-right corner toast bar that shows running/complete/error status for all background processes — cron jobs, memory compaction, knowledge extraction, and any custom jobs. Includes gateway cron event enrichment (job name forwarded in broadcast payload) and context gauge button with live spinner + progress modal. Use when adding or improving background job visibility in the OpenClaw UI, fixing 'Background job' fallback names on toasts, or troubleshooting the compaction button showing no progress.

Safety Notice

This listing is from the official public ClawHub registry. Review SKILL.md and referenced scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "bg-job-toasts" with this command: npx skills add maverick-software/bg-job-toasts

Background Job Toasts — OpenClaw Control UI

Real-time bottom-right toast bar that shows running / complete / error status for all background processes in the OpenClaw Control UI: cron jobs, memory compaction, knowledge extraction, and any consumer that pushes to backgroundJobToasts.

Status: ✅ Active

ComponentStatus
Toast bar (bottom-right)✅ Working
Cron job name enrichment (gateway)✅ Working
Context gauge spinner during compaction✅ Working
Compaction modal phase labels + progress bar✅ Working
Auto-dismiss after completion✅ Working

Architecture

Data Type (ui/src/ui/app-view-state.ts)

export type BackgroundJobToast = {
  jobId: string;        // Unique identifier (use "cmp-" prefix for compaction)
  jobName: string;      // Human-readable label shown in the toast
  status: "running" | "complete" | "error";
  startedAt: number;    // Date.now()
  completedAt: number | null;
  errorMsg?: string;
};

backgroundJobToasts: BackgroundJobToast[] is a LitElement @state() property on OpenClawApp (app.ts). Assigning to it directly triggers an automatic re-render — no requestUpdate() needed for the toast array itself.

Toast Rendering (ui/src/ui/app-render.ts)

renderBackgroundJobToasts(state) renders the .bg-job-toasts container at the bottom of the app shell (called in the root render() function). It maps each toast to a .bg-job-toast--{status} div with an icon, name, and label.

Status indicators:

  • running → spinner icon (CSS animation compaction-spin), blue tint, "running..."
  • complete → checkmark icon, green tint, "done"
  • error → X icon, red tint, "failed"

Styles (ui/src/styles/components.css)

.bg-job-toasts   { position: fixed; bottom: 24px; right: 24px; z-index: 9000; … }
.bg-job-toast    { border-radius: var(--radius-lg); background: var(--panel-strong); … }
.bg-job-toast--running  { color: var(--info); … }
.bg-job-toast--complete { color: var(--ok); … }
.bg-job-toast--error    { color: var(--danger); … }

Entry animation: bg-job-toast-in (0.2s, slide up from 6px).

Gateway Cron Event Enrichment (src/gateway/server-cron.ts)

The gateway's onEvent callback enriches cron events with the job name before broadcasting, so the UI can display a meaningful label even when the Cron tab hasn't been opened yet (and thus cronJobs isn't loaded):

onEvent: (evt) => {
  const jobForName = cron.getJob(evt.jobId);
  const enriched = jobForName?.name ? { ...evt, name: jobForName.name } : evt;
  params.broadcast("cron", enriched, { dropIfSlow: true });
  // ... rest of webhook handling
}

UI Cron Event Handler (ui/src/ui/app-gateway.ts)

const cronPayload = evt.payload as {
  jobId?: string;
  name?: string;       // ← enriched by gateway
  action?: string;
  status?: string;
  error?: string;
};
// Priority: event.name > loaded cronJobs list > fallback
const jobName = cronPayload.name ?? matchedJob?.name ?? "Background job";

Context Gauge + Compaction Button (ui/src/ui/app-render.helpers.ts)

The renderContextGauge(state) function renders a circular SVG ring in the chat toolbar. When compaction is running:

  • The gauge ring is replaced by a spinning icon
  • A compactState.phase overlay modal appears with live status

Phase variables must be defined before the template:

const phaseLabel =
  compactState.phase === "running"
    ? "Summarizing your conversation…"
    : compactState.phase === "complete"
      ? "Done! Context has been reduced."
      : compactState.phase === "error"
        ? "Something went wrong."
        : "";
const isWorking = compactState.phase === "running";

compactState is read from (state as unknown as Record<string, unknown>).__compactState — a plain property (not @state()), so app.requestUpdate() must be called explicitly after mutations.

Compaction flow:

  1. Button click → __compactState = { active: true, phase: "running" } + push "running" toast → requestUpdate()
  2. RPC sessions.compact resolves → update toast to "complete" with token counts → __compactState = {}requestUpdate()
  3. Auto-dismiss toast after 5 seconds

Files Modified

FileChange
src/gateway/server-cron.tsEnrich cron event with name before broadcasting
ui/src/ui/app-gateway.tsRead cronPayload.name first in job name resolution
ui/src/ui/app-render.helpers.tsDefine phaseLabel/isWorking; spinner on gauge button while running

How to Push a Custom Toast from Anywhere

Any code with access to the OpenClawApp instance can push a toast:

const app = state as unknown as OpenClawApp;
const jobId = "my-job-" + Date.now();

// Start
app.backgroundJobToasts = [
  ...(app.backgroundJobToasts ?? []).filter(t => t.jobId !== jobId),
  { jobId, jobName: "My Task", status: "running", startedAt: Date.now(), completedAt: null },
];

// Complete (with auto-dismiss)
app.backgroundJobToasts = [
  ...(app.backgroundJobToasts ?? []).filter(t => t.jobId !== jobId),
  { jobId, jobName: "My Task done", status: "complete", startedAt: Date.now(), completedAt: Date.now() },
];
window.setTimeout(() => {
  app.backgroundJobToasts = (app.backgroundJobToasts ?? []).filter(t => t.jobId !== jobId);
}, 5000);

Prefix convention: Use "cmp-" for compaction jobs (filtered when a new compaction starts). Use a unique domain prefix for other job types.

Known Gotchas

  • phaseLabel/isWorking must be defined before the template literal — referencing them without defining them compiles fine (TypeScript doesn't catch undeclared variables inside template literals) but renders as undefined at runtime, producing a blank modal with no text and no progress bar.
  • __compactState is not @state() — always call app.requestUpdate() after mutating it, or the UI won't re-render.
  • Cron job name fallback order matters — the name field in the event payload (gateway-enriched) must be checked before the local cronJobs list, because the list is only loaded when the Cron tab is open.
  • backgroundJobToasts IS @state() — assigning the array triggers a re-render automatically; no requestUpdate() needed specifically for that.

Reference

See references/source-snapshot.md for a snapshot of the key code sections at time of writing.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

soul-agent

Make your agent 'live beside you' with heartbeats, mood system, relationship evolution, and independent memory. Use for creating a digital companion with its...

Registry SourceRecently Updated
Automation

Sparkey

Provides time-limited, self-revoking SSH access for AI agents using certificate TTL, user expiry, forced command restrictions, and scheduled automated cleanup.

Registry SourceRecently Updated
Automation

WTT Skill

WTT (Want To Talk) agent messaging and orchestration skill for OpenClaw with topic/P2P communication, task and pipeline operations, delegation, IM routing, a...

Registry SourceRecently Updated
1981cecwxf
Automation

Cyber Horn

Turn text into spoken Feishu (Lark) voice messages. Use when the agent should speak in a Feishu group, send voice alerts or announcements, or reply with a pl...

Registry SourceRecently Updated