mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-28 12:34:50 +02:00
[aidan] feat/workflow-edit: add draft testing save flow
This commit is contained in:
@@ -68,6 +68,9 @@ export interface AgentSession {
|
||||
id: string;
|
||||
name: string;
|
||||
status: 'draft' | 'running' | 'waiting_approval' | 'completed' | 'error' | 'stopped';
|
||||
/** For workflow Test Agent sessions: drives the test card's footer
|
||||
* (running -> red Force Stop; complete/error -> green close). */
|
||||
workflow_test_state?: 'running' | 'complete' | 'error' | null;
|
||||
provider: string;
|
||||
model: string;
|
||||
mode: string;
|
||||
@@ -703,6 +706,14 @@ const agentsSlice = createSlice({
|
||||
}
|
||||
},
|
||||
|
||||
setSessionTestState(
|
||||
state,
|
||||
action: PayloadAction<{ sessionId: string; state: 'running' | 'complete' | 'error' }>
|
||||
) {
|
||||
const session = state.sessions[action.payload.sessionId];
|
||||
if (session) session.workflow_test_state = action.payload.state;
|
||||
},
|
||||
|
||||
setSessionConnState(
|
||||
state,
|
||||
action: PayloadAction<{ sessionId: string; state: 'live' | 'reconnecting' }>
|
||||
@@ -1408,6 +1419,7 @@ export const {
|
||||
updateSession,
|
||||
updateSessionStatus,
|
||||
setSessionConnState,
|
||||
setSessionTestState,
|
||||
addMessage,
|
||||
addOptimisticMessage,
|
||||
markOptimisticFailed,
|
||||
|
||||
@@ -81,6 +81,12 @@ export interface Workflow {
|
||||
edit_agent_session_id?: string | null;
|
||||
/** Sticky session id for the embedded scheduling agent (cadence -> gated tool call). */
|
||||
schedule_agent_session_id?: string | null;
|
||||
/** Pending Edit-Agent draft of the steps; present only while editing. */
|
||||
draft_steps?: WorkflowStep[] | null;
|
||||
/** True when a draft is staged (server-computed convenience flag). */
|
||||
has_draft?: boolean;
|
||||
/** Most recent Test Agent session for this workflow. */
|
||||
last_test_session_id?: string | null;
|
||||
/** Tool permissions the user answered once and we reuse on later runs so an
|
||||
* unattended scheduled fire doesn't stall on a prompt. tool name -> answer. */
|
||||
remembered_approvals?: Record<string, 'allow' | 'deny'>;
|
||||
@@ -133,6 +139,9 @@ export interface OpenCard {
|
||||
sidecarKind?: 'watching' | 'viewing-completed' | 'viewing-error' | 'testing' | null;
|
||||
/** Per-step expand state for ExpandedView. Stores step ids. */
|
||||
expandedStepIds?: string[];
|
||||
/** One-shot "Schedule this workflow?" prompt shown right after a convert.
|
||||
* Transient: lives only on the just-created card, never on hub-opened ones. */
|
||||
showScheduleNudge?: boolean;
|
||||
/** Pre-seed message for the Fix-with-Agent flow so the EditAgent composer
|
||||
* knows which failure context to lead with. Cleared once consumed. */
|
||||
fixSeed?: { runId: string; stepIdx: number; stepLabel: string; error: string } | null;
|
||||
@@ -210,6 +219,18 @@ export const updateWorkflow = createAsyncThunk<
|
||||
},
|
||||
);
|
||||
|
||||
export const commitDraft = createAsyncThunk('workflows/commitDraft', async (id: string) => {
|
||||
const res = await fetch(`${API}/${id}/draft/commit`, { method: 'POST' });
|
||||
if (!res.ok) throw new Error(`commit failed ${res.status}`);
|
||||
return (await res.json()) as Workflow;
|
||||
});
|
||||
|
||||
export const discardDraft = createAsyncThunk('workflows/discardDraft', async (id: string) => {
|
||||
const res = await fetch(`${API}/${id}/draft/discard`, { method: 'POST' });
|
||||
if (!res.ok) throw new Error(`discard failed ${res.status}`);
|
||||
return (await res.json()) as Workflow;
|
||||
});
|
||||
|
||||
export const deleteWorkflow = createAsyncThunk('workflows/delete', async (id: string) => {
|
||||
await fetch(`${API}/${id}`, { method: 'DELETE' });
|
||||
return id;
|
||||
@@ -364,6 +385,8 @@ const slice = createSlice({
|
||||
.addCase(fetchWorkflows.rejected, (state) => { state.loading = false; state.loaded = true; })
|
||||
.addCase(createWorkflow.fulfilled, (state, action) => { state.items[action.payload.id] = action.payload; })
|
||||
.addCase(updateWorkflow.fulfilled, (state, action) => { state.items[action.payload.id] = action.payload; })
|
||||
.addCase(commitDraft.fulfilled, (state, action) => { state.items[action.payload.id] = action.payload; })
|
||||
.addCase(discardDraft.fulfilled, (state, action) => { state.items[action.payload.id] = action.payload; })
|
||||
.addCase(deleteWorkflow.fulfilled, (state, action) => {
|
||||
delete state.items[action.payload];
|
||||
delete state.runs[action.payload];
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
addApprovalRequest,
|
||||
removeApprovalRequest,
|
||||
updateSessionStatus,
|
||||
setSessionTestState,
|
||||
updateSessionCost,
|
||||
updateSessionContext,
|
||||
setContextOverflow,
|
||||
@@ -444,6 +445,13 @@ class WebSocketManager {
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case 'agent:test_state':
|
||||
// broadcast_global puts everything under data (no top-level session_id).
|
||||
if (data.session_id && data.state) {
|
||||
store.dispatch(setSessionTestState({ sessionId: data.session_id, state: data.state }));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'agent:status':
|
||||
// Capture pre-transition status so we only fire a system notification
|
||||
// on a real running→terminal transition. Otherwise a session that
|
||||
|
||||
Reference in New Issue
Block a user