Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,24 @@ import { strict as assert } from 'node:assert';
import { describe, it } from 'node:test';
import {
dismissAgentGraphPanel,
isAgentGraphLive,
isAgentGraphPanelDismissible,
reconcileAgentGraphPanelDismissals,
shouldShowAgentGraphPanel,
} from '../../renderer/agent-graph-panel-visibility.js';

describe('isAgentGraphLive', () => {
it('treats in-flight statuses as live and settled ones as not', () => {
for (const status of ['active', 'waiting', 'closing'] as const) {
assert.equal(isAgentGraphLive(status), true, status);
}
for (const status of ['empty', 'stopped', 'failed', 'completed'] as const) {
assert.equal(isAgentGraphLive(status), false, status);
}
assert.equal(isAgentGraphLive(undefined), false);
});
});

describe('isAgentGraphPanelDismissible', () => {
it('allows hiding a graph that no longer has active work', () => {
assert.equal(isAgentGraphPanelDismissible('completed'), true);
Expand Down
11 changes: 11 additions & 0 deletions apps/desktop/src/renderer/agent-graph-panel-visibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ export function isAgentGraphPanelDismissible(
return status !== undefined && DISMISSIBLE_STATUSES.has(status);
}

const LIVE_STATUSES = new Set<AgentGraphPanelStatus>([
'active',
'waiting',
'closing',
]);

/** A graph in one of these statuses can be stopped and must keep signaling liveness. */
export function isAgentGraphLive(status: AgentGraphPanelStatus | undefined): boolean {
return status !== undefined && LIVE_STATUSES.has(status);
}

export function dismissAgentGraphPanel(
dismissedBySession: AgentGraphPanelDismissals,
sessionId: string,
Expand Down
12 changes: 11 additions & 1 deletion apps/desktop/src/renderer/agent-graph-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { EmptyState } from '@astryxdesign/core/EmptyState';
import { Spinner } from '@astryxdesign/core/Spinner';
import {
dismissAgentGraphPanel,
isAgentGraphLive,
isAgentGraphPanelDismissible,
reconcileAgentGraphPanelDismissals,
shouldShowAgentGraphPanel,
Expand Down Expand Up @@ -204,6 +205,7 @@ export function AgentGraphPanel(props: {
stopState.rootSessionId === props.rootSessionId && stopState.graphId === selectedGraphId;
const stopPending = stopFeedbackMatchesSelection && stopState.pending;
const stopError = stopFeedbackMatchesSelection && stopState.error;
const graphLive = snapshot !== undefined && isAgentGraphLive(snapshot.status);

useEffect(() => {
setSnapshot(undefined);
Expand Down Expand Up @@ -350,7 +352,7 @@ export function AgentGraphPanel(props: {
!loading &&
snapshot !== undefined &&
snapshot.graphId === selectedGraphId &&
['active', 'waiting', 'closing'].includes(snapshot.status);
isAgentGraphLive(snapshot.status);
const dismissAvailable =
selectedEpoch?.current === true &&
!loading &&
Expand Down Expand Up @@ -393,6 +395,14 @@ export function AgentGraphPanel(props: {
) : null}
{snapshot ? (
<span className="maka-agent-graph-progress">
{graphLive ? (
<Spinner
size="sm"
shade="subtle"
className="maka-agent-graph-heartbeat"
aria-hidden="true"
/>
) : null}
{copy.status(snapshot.status)} ·{' '}
{copy.progress(
progress.settled,
Expand Down
20 changes: 20 additions & 0 deletions apps/desktop/src/renderer/styles/agent-graph.css
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@
color: var(--muted-foreground);
}

/* Inline flex so the heartbeat spinner stays on the status line and spacing is
gap-driven; the element also holds plain text nodes. */
.maka-agent-graph-progress {
display: inline-flex;
align-items: center;
gap: var(--space-1);
}

.maka-agent-graph-empty {
margin: var(--space-2) 0 0;
}
Expand Down Expand Up @@ -125,9 +133,21 @@
background: var(--muted-foreground);
}

@keyframes maka-agent-graph-dot-pulse {
0%,
100% {
opacity: 1;
}

50% {
opacity: 0.35;
}
}

.maka-agent-graph-operators li[data-status="running"] .maka-agent-graph-status-dot,
.maka-agent-graph-operators li[data-status="runnable"] .maka-agent-graph-status-dot {
background: var(--status-running);
animation: maka-agent-graph-dot-pulse 1.6s ease-in-out infinite;
}

.maka-agent-graph-operators li[data-status="completed"] .maka-agent-graph-status-dot {
Expand Down