mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-17 18:25:42 +02:00
[eric] workflows: a running workflow gets a real agent card, so its browsers dock inside it and are reaped with it; the card goes when the run stops (ENG-256)
This commit is contained in:
@@ -24,7 +24,7 @@ import {
|
||||
} from '@/shared/state/dashboardLayoutSlice';
|
||||
import { fetchOutputs, type Output } from '@/shared/state/outputsSlice';
|
||||
import { generateDashboardName } from '@/shared/state/dashboardsSlice';
|
||||
import { isUserLaunchedSession } from '@/shared/state/isUserLaunchedSession';
|
||||
import { deservesCanvasCard } from '@/shared/state/isUserLaunchedSession';
|
||||
import { REVEAL_MIN_ZOOM } from '../../canvas/revealZoom';
|
||||
import { fetchWorkflows, fetchAllRuns, fetchActiveRuns } from '@/shared/state/workflowsSlice';
|
||||
import { fetchMissedRuns } from '@/shared/state/missedRunsSlice';
|
||||
@@ -326,7 +326,7 @@ export function useDashboardLifecycle({
|
||||
useEffect(() => {
|
||||
if (!layoutInitialized) return;
|
||||
const dashboardSessionIds = Object.values(sessions)
|
||||
.filter((s) => s.dashboard_id === dashboardId && isUserLaunchedSession(s))
|
||||
.filter((s) => s.dashboard_id === dashboardId && deservesCanvasCard(s))
|
||||
.map((s) => s.id);
|
||||
const liveIds = dashboardSessionIds.sort().join(',');
|
||||
if (liveIds === prevSessionIdsRef.current) return;
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// Run: node --test frontend/src/shared/state/deservesCanvasCard.test.ts
|
||||
//
|
||||
// ENG-256. A workflow run was denied a canvas card, and the browser-docking path is gated on the
|
||||
// parent card existing, so its browsers spawned as loose windows nothing owned and nothing tore
|
||||
// down. ENG-248/249/250 were all symptoms of that one missing object. The rule now: a run gets a
|
||||
// real card while it is running, and loses it when it stops, so a nightly workflow does not leave a
|
||||
// card behind every night.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { deservesCanvasCard, isUserLaunchedSession } from './isUserLaunchedSession.ts';
|
||||
|
||||
const chat = { mode: 'agent', status: 'completed' };
|
||||
const run = (status: string) => ({ mode: 'agent', status, workflow_run_id: 'run-1' });
|
||||
|
||||
test('an ordinary chat always has a card, running or not', () => {
|
||||
assert.equal(deservesCanvasCard(chat), true);
|
||||
assert.equal(deservesCanvasCard({ mode: 'agent', status: 'running' }), true);
|
||||
});
|
||||
|
||||
test('a workflow run has a card while it works', () => {
|
||||
assert.equal(deservesCanvasCard(run('running')), true);
|
||||
assert.equal(deservesCanvasCard(run('waiting_approval')), true);
|
||||
});
|
||||
|
||||
test('a workflow run loses its card the moment it stops', () => {
|
||||
for (const s of ['completed', 'failed', 'stopped', 'error', '']) {
|
||||
assert.equal(deservesCanvasCard(run(s)), false, `status ${s || '(empty)'}`);
|
||||
}
|
||||
assert.equal(deservesCanvasCard({ mode: 'agent', workflow_run_id: 'run-1' }), false, 'no status at all');
|
||||
});
|
||||
|
||||
test('plumbing chats never get a card, however they look', () => {
|
||||
for (const mode of ['browser-agent', 'invoked-agent', 'sub-agent']) {
|
||||
assert.equal(deservesCanvasCard({ mode, status: 'running' }), false, mode);
|
||||
}
|
||||
assert.equal(deservesCanvasCard({ mode: 'agent', status: 'running', workflow_edit_id: 'e1' }), false);
|
||||
});
|
||||
|
||||
test('notifications are still a separate question from cards', () => {
|
||||
// A running workflow now earns a CARD but must not start earning notifications: nobody asked for
|
||||
// a ping every time a scheduled job fires.
|
||||
assert.equal(deservesCanvasCard(run('running')), true);
|
||||
assert.equal(isUserLaunchedSession(run('running')), false);
|
||||
});
|
||||
@@ -8,7 +8,23 @@ export interface SessionOrigin {
|
||||
workflow_edit_id?: string | null;
|
||||
}
|
||||
|
||||
/** True for a chat the user started themselves, which is the only kind that earns a card or a notification. */
|
||||
/** A run that is still working, so its card is worth having on screen. */
|
||||
const LIVE_STATUSES: ReadonlySet<string> = new Set(['running', 'waiting_approval']);
|
||||
|
||||
/** True for a chat the user started themselves, which is the only kind that earns a notification. */
|
||||
export function isUserLaunchedSession(session: SessionOrigin): boolean {
|
||||
return !session.workflow_run_id && !session.workflow_edit_id && !PLUMBING_MODES.has(session.mode);
|
||||
}
|
||||
|
||||
/** True for a session that should have a card on the canvas right now.
|
||||
*
|
||||
* A workflow run earns one WHILE IT RUNS. Denying it a card is what made its browsers spawn as loose
|
||||
* canvas windows that nothing owned and nothing tore down (ENG-248/249/250 were all symptoms of it):
|
||||
* the dock path is gated on the parent card existing, so no card meant no owner. Give the run a card
|
||||
* and the existing inline-dock plus despawn logic applies to it unchanged. The card goes when the run
|
||||
* stops, or a nightly workflow would leave one behind every single night.
|
||||
*/
|
||||
export function deservesCanvasCard(session: SessionOrigin & { status?: string }): boolean {
|
||||
if (isUserLaunchedSession(session)) return true;
|
||||
return !!session.workflow_run_id && LIVE_STATUSES.has(session.status ?? '');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user