feat(vwb): PauseDialog + ChecklistPanel + extension PropertiesPanel pour safety_checks
PauseDialog (composant nouveau) : - 2 modes selon payload : bulle simple legacy si safety_checks vide, ChecklistPanel sinon - Continuer désactivé tant que required non cochés - Badge [obligatoire] et [Léa] (avec evidence en tooltip) - POST /api/v3/replay/resume avec acknowledged_check_ids quand replay_id présent, fallback api.resumeExecution() pour la voie locale types.ts : SafetyCheck, SafetyLevel, extension Execution (pause_reason, pause_message, safety_checks, replay_id, status 'paused_need_help'). Action pause_for_human enrichie de safety_level et safety_checks dans le catalogue ACTIONS. PropertiesPanel : éditeur safety_level (dropdown standard/medical_critical) + liste éditable de safety_checks (id/label/required + ajout/suppression). App.tsx : rendu conditionnel du PauseDialog en overlay quand status == paused_need_help, ou paused avec safety_checks. Backward 100% : workflows existants sans safety_checks affichent la bulle legacy. CSS : .pause-dialog-overlay/.pause-dialog-checks/.checklist-panel/ .check-item/.badge-required/.badge-lea/.check-editor-row. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@ import ExecutionOverlay from './components/ExecutionOverlay';
|
|||||||
import type { Variable } from './components/VariableManager';
|
import type { Variable } from './components/VariableManager';
|
||||||
import RightPanel from './components/RightPanel';
|
import RightPanel from './components/RightPanel';
|
||||||
import SelfHealingDialog from './components/SelfHealingDialog';
|
import SelfHealingDialog from './components/SelfHealingDialog';
|
||||||
|
import PauseDialog from './components/PauseDialog';
|
||||||
import ConfidenceDashboard from './components/ConfidenceDashboard';
|
import ConfidenceDashboard from './components/ConfidenceDashboard';
|
||||||
import WorkflowValidation from './components/WorkflowValidation';
|
import WorkflowValidation from './components/WorkflowValidation';
|
||||||
import ReviewModal from './components/ReviewModal';
|
import ReviewModal from './components/ReviewModal';
|
||||||
@@ -569,6 +570,47 @@ function App() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* QW4 — Pause supervisée (safety_checks).
|
||||||
|
Affiché si le serveur renvoie status == paused_need_help, ou
|
||||||
|
status == paused avec un payload de checks. Backward 100% : si
|
||||||
|
safety_checks vide, PauseDialog rend la bulle simple legacy. */}
|
||||||
|
{(appState?.execution?.status === 'paused_need_help' ||
|
||||||
|
(appState?.execution?.status === 'paused' &&
|
||||||
|
(appState?.execution?.safety_checks?.length ?? 0) > 0)) && (
|
||||||
|
<div className="pause-dialog-overlay">
|
||||||
|
<PauseDialog
|
||||||
|
pauseMessage={appState.execution.pause_message || 'Validation requise'}
|
||||||
|
pauseReason={appState.execution.pause_reason}
|
||||||
|
safetyChecks={appState.execution.safety_checks || []}
|
||||||
|
onResume={async (ackIds) => {
|
||||||
|
const replayId = appState.execution?.replay_id || appState.execution?.id;
|
||||||
|
if (replayId) {
|
||||||
|
// Voie streaming server (Agent V1 / replay distant)
|
||||||
|
const resp = await fetch('/api/v3/replay/resume', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
replay_id: replayId,
|
||||||
|
acknowledged_check_ids: ackIds,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (!resp.ok) {
|
||||||
|
const err = await resp.json().catch(() => ({}));
|
||||||
|
throw new Error(err?.detail?.error || resp.statusText);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Voie locale (execute/resume)
|
||||||
|
await api.resumeExecution();
|
||||||
|
}
|
||||||
|
await loadState();
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
handleStopExecution();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* ConfidenceDashboard déplacé dans le header */}
|
{/* ConfidenceDashboard déplacé dans le header */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
// QW4 — PauseDialog : bulle de pause supervisée avec ChecklistPanel intégré.
|
||||||
|
//
|
||||||
|
// 2 modes de rendu :
|
||||||
|
// - safety_checks vide -> bulle simple legacy (Continuer / Annuler)
|
||||||
|
// - safety_checks fournis -> ChecklistPanel ; bouton Continuer désactivé
|
||||||
|
// tant qu'un check `required` n'est pas coché.
|
||||||
|
//
|
||||||
|
// Les checks `llm_contextual` portent un badge [Léa] avec evidence en tooltip.
|
||||||
|
|
||||||
|
import { useState, useMemo } from 'react';
|
||||||
|
import type { SafetyCheck } from '../types';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
pauseMessage: string;
|
||||||
|
pauseReason?: string;
|
||||||
|
safetyChecks: SafetyCheck[];
|
||||||
|
onResume: (acknowledgedIds: string[]) => Promise<void>;
|
||||||
|
onCancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PauseDialog({
|
||||||
|
pauseMessage,
|
||||||
|
pauseReason,
|
||||||
|
safetyChecks,
|
||||||
|
onResume,
|
||||||
|
onCancel,
|
||||||
|
}: Props) {
|
||||||
|
const [checked, setChecked] = useState<Record<string, boolean>>({});
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const allRequiredOK = useMemo(() => {
|
||||||
|
return safetyChecks
|
||||||
|
.filter((c) => c.required)
|
||||||
|
.every((c) => checked[c.id] === true);
|
||||||
|
}, [safetyChecks, checked]);
|
||||||
|
|
||||||
|
const toggle = (id: string) => {
|
||||||
|
setChecked((prev) => ({ ...prev, [id]: !prev[id] }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResume = async () => {
|
||||||
|
setSubmitting(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const acknowledgedIds = Object.entries(checked)
|
||||||
|
.filter(([, v]) => v)
|
||||||
|
.map(([k]) => k);
|
||||||
|
await onResume(acknowledgedIds);
|
||||||
|
} catch (e: any) {
|
||||||
|
setError(e?.message || 'Erreur lors de la reprise');
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Backward compat : pas de checks -> bulle simple legacy
|
||||||
|
if (safetyChecks.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="pause-dialog-simple">
|
||||||
|
<p>{pauseMessage}</p>
|
||||||
|
{pauseReason && <small className="pause-reason">Raison : {pauseReason}</small>}
|
||||||
|
<div className="pause-actions">
|
||||||
|
<button onClick={() => onResume([])} disabled={submitting}>
|
||||||
|
Continuer
|
||||||
|
</button>
|
||||||
|
<button onClick={onCancel} disabled={submitting}>
|
||||||
|
Annuler
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pause-dialog-checks">
|
||||||
|
<h3>Pause supervisée</h3>
|
||||||
|
<p className="pause-message">{pauseMessage}</p>
|
||||||
|
{pauseReason && (
|
||||||
|
<div className="pause-reason-banner">
|
||||||
|
<strong>Raison :</strong> {pauseReason}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ul className="checklist-panel">
|
||||||
|
{safetyChecks.map((c) => (
|
||||||
|
<li key={c.id} className={`check-item ${c.required ? 'required' : 'optional'}`}>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={!!checked[c.id]}
|
||||||
|
onChange={() => toggle(c.id)}
|
||||||
|
disabled={submitting}
|
||||||
|
/>
|
||||||
|
<span className="check-label">{c.label}</span>
|
||||||
|
{c.required && <span className="badge badge-required">obligatoire</span>}
|
||||||
|
{c.source === 'llm_contextual' && (
|
||||||
|
<span className="badge badge-lea" title={c.evidence || ''}>
|
||||||
|
Léa
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
{c.source === 'llm_contextual' && c.evidence && (
|
||||||
|
<small className="check-evidence">-> {c.evidence}</small>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{error && <div className="pause-error">{error}</div>}
|
||||||
|
|
||||||
|
<div className="pause-actions">
|
||||||
|
<button
|
||||||
|
onClick={handleResume}
|
||||||
|
disabled={!allRequiredOK || submitting}
|
||||||
|
title={!allRequiredOK ? 'Coche tous les checks obligatoires' : 'Reprendre le replay'}
|
||||||
|
>
|
||||||
|
{submitting ? 'Reprise...' : 'Continuer'}
|
||||||
|
</button>
|
||||||
|
<button onClick={onCancel} disabled={submitting}>
|
||||||
|
Annuler
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1353,6 +1353,136 @@ export default function PropertiesPanel({ step, onUpdateParams, onDelete }: Prop
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
case 'pause_for_human': {
|
||||||
|
// QW4 — éditeur safety_level + safety_checks (déclaratifs)
|
||||||
|
const safetyChecks = Array.isArray(params.safety_checks)
|
||||||
|
? (params.safety_checks as Array<{ id?: string; label?: string; required?: boolean }>)
|
||||||
|
: [];
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="prop-field">
|
||||||
|
<label>Message affiché à l'opérateur</label>
|
||||||
|
<textarea
|
||||||
|
rows={4}
|
||||||
|
value={String(params.message || '')}
|
||||||
|
onChange={(e) => updateParam('message', e.target.value)}
|
||||||
|
placeholder="Ex: Décision : {{dec.decision}} {{dec.justification}}"
|
||||||
|
style={{ width: '100%', fontFamily: 'monospace', fontSize: '12px' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* QW4 — Niveau de sécurité */}
|
||||||
|
<div className="prop-field">
|
||||||
|
<label>Niveau de sécurité</label>
|
||||||
|
<select
|
||||||
|
value={String(params.safety_level || 'standard')}
|
||||||
|
onChange={(e) => updateParam('safety_level', e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="standard">Standard (pas de LLM)</option>
|
||||||
|
<option value="medical_critical">Médical critique (LLM contextuel)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* QW4 — Liste éditable de checks déclaratifs */}
|
||||||
|
<div className="prop-field">
|
||||||
|
<label>Checks à valider (déclaratifs)</label>
|
||||||
|
{safetyChecks.map((check, i) => (
|
||||||
|
<div key={i} className="check-editor-row">
|
||||||
|
<input
|
||||||
|
placeholder="ID (ex: check_ipp)"
|
||||||
|
value={check.id || ''}
|
||||||
|
style={{ width: '30%' }}
|
||||||
|
onChange={(e) => {
|
||||||
|
const next = [...safetyChecks];
|
||||||
|
next[i] = { ...check, id: e.target.value };
|
||||||
|
updateParam('safety_checks', next);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
placeholder="Libellé"
|
||||||
|
value={check.label || ''}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
onChange={(e) => {
|
||||||
|
const next = [...safetyChecks];
|
||||||
|
next[i] = { ...check, label: e.target.value };
|
||||||
|
updateParam('safety_checks', next);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={!!check.required}
|
||||||
|
onChange={(e) => {
|
||||||
|
const next = [...safetyChecks];
|
||||||
|
next[i] = { ...check, required: e.target.checked };
|
||||||
|
updateParam('safety_checks', next);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
Obligatoire
|
||||||
|
</label>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
const next = safetyChecks.filter((_, j) => j !== i);
|
||||||
|
updateParam('safety_checks', next);
|
||||||
|
}}
|
||||||
|
title="Supprimer ce check"
|
||||||
|
>
|
||||||
|
−
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
const next = [
|
||||||
|
...safetyChecks,
|
||||||
|
{ id: '', label: '', required: true },
|
||||||
|
];
|
||||||
|
updateParam('safety_checks', next);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+ Ajouter un check
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 't2a_decision':
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="prop-field">
|
||||||
|
<label>Template d'entrée (supporte {'{{var}}'})</label>
|
||||||
|
<textarea
|
||||||
|
rows={5}
|
||||||
|
value={String(params.input_template || '')}
|
||||||
|
onChange={(e) => updateParam('input_template', e.target.value)}
|
||||||
|
placeholder={'{{t0}}\n---\n{{t1}}\n{{t2}}\n{{t3}}\n{{t4}}'}
|
||||||
|
style={{ width: '100%', fontFamily: 'monospace', fontSize: '12px' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="prop-field">
|
||||||
|
<label>Variable de sortie (ex: dec)</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={String(params.output_var || '')}
|
||||||
|
onChange={(e) => updateParam('output_var', e.target.value)}
|
||||||
|
placeholder="dec"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="prop-field">
|
||||||
|
<label>Modèle Ollama</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={String(params.model || 'qwen2.5:7b')}
|
||||||
|
onChange={(e) => updateParam('model', e.target.value)}
|
||||||
|
placeholder="qwen2.5:7b"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return <div className="prop-info">Pas de paramètres supplémentaires</div>;
|
return <div className="prop-info">Pas de paramètres supplémentaires</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4491,3 +4491,86 @@ body {
|
|||||||
.right-panel-tabbed .capture-library {
|
.right-panel-tabbed .capture-library {
|
||||||
border-top: 1px solid var(--border);
|
border-top: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* === QW4 — PauseDialog & ChecklistPanel === */
|
||||||
|
.pause-dialog-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(15, 23, 42, 0.45);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
.pause-dialog-simple,
|
||||||
|
.pause-dialog-checks {
|
||||||
|
padding: 16px;
|
||||||
|
max-width: 480px;
|
||||||
|
background: #fff;
|
||||||
|
border: 2px solid #f59e0b;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
.pause-dialog-checks h3 { margin: 0 0 8px; color: #92400e; }
|
||||||
|
.pause-message { margin: 0 0 12px; }
|
||||||
|
.pause-reason-banner {
|
||||||
|
background: #fef3c7;
|
||||||
|
padding: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.pause-reason { color: #6b7280; display: block; margin-top: 4px; }
|
||||||
|
.checklist-panel {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 0 12px;
|
||||||
|
}
|
||||||
|
.check-item {
|
||||||
|
padding: 6px 0;
|
||||||
|
border-bottom: 1px solid #f3f4f6;
|
||||||
|
}
|
||||||
|
.check-item.required { background: #fef9c3; }
|
||||||
|
.check-item label {
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.badge {
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
.badge-required { background: #dc2626; color: #fff; }
|
||||||
|
.badge-lea { background: #2563eb; color: #fff; cursor: help; }
|
||||||
|
.check-evidence {
|
||||||
|
display: block;
|
||||||
|
font-style: italic;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-left: 24px;
|
||||||
|
}
|
||||||
|
.pause-error {
|
||||||
|
color: #dc2626;
|
||||||
|
padding: 8px;
|
||||||
|
background: #fef2f2;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.pause-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
.pause-actions button:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* QW4 — éditeur de safety_checks dans PropertiesPanel */
|
||||||
|
.check-editor-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
// Types pour l'API v3
|
// Types pour l'API v3
|
||||||
|
|
||||||
|
// === QW4 — Safety checks (pause supervisée) ===
|
||||||
|
export type SafetyLevel = 'standard' | 'medical_critical';
|
||||||
|
|
||||||
|
export interface SafetyCheck {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
required: boolean;
|
||||||
|
source: 'declarative' | 'llm_contextual';
|
||||||
|
evidence?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
// Mode d'exécution
|
// Mode d'exécution
|
||||||
export type ExecutionMode = 'basic' | 'intelligent' | 'debug' | 'verified';
|
export type ExecutionMode = 'basic' | 'intelligent' | 'debug' | 'verified';
|
||||||
|
|
||||||
@@ -133,7 +144,9 @@ export const ACTIONS: ActionDefinition[] = [
|
|||||||
{ name: 'max_iterations', type: 'number', description: 'Nombre maximum d\'itérations' }
|
{ name: 'max_iterations', type: 'number', description: 'Nombre maximum d\'itérations' }
|
||||||
] },
|
] },
|
||||||
{ type: 'pause_for_human', label: 'Pause supervisée', icon: '⏸', description: 'Léa s\'arrête et demande validation humaine via une bulle interactive (boutons Continuer / Annuler).', category: 'logic', needsAnchor: false, params: [
|
{ type: 'pause_for_human', label: 'Pause supervisée', icon: '⏸', description: 'Léa s\'arrête et demande validation humaine via une bulle interactive (boutons Continuer / Annuler).', category: 'logic', needsAnchor: false, params: [
|
||||||
{ name: 'message', type: 'string', description: 'Message affiché dans la bulle (ex: "Je ne suis pas sûre du critère 3, validez-vous UHCD ?")' }
|
{ name: 'message', type: 'string', description: 'Message affiché dans la bulle (ex: "Je ne suis pas sûre du critère 3, validez-vous UHCD ?")' },
|
||||||
|
{ name: 'safety_level', type: 'select', description: 'Niveau de sécurité : standard (pas de LLM) ou medical_critical (LLM contextuel)' },
|
||||||
|
{ name: 'safety_checks', type: 'safety_checks_editor', description: 'Liste de checks à valider avant reprise (id, libellé, obligatoire ?). Édité dans le panneau Propriétés.' }
|
||||||
] },
|
] },
|
||||||
{ type: 't2a_decision', label: 'Décision T2A (LLM)', icon: '🧠', description: 'Analyse un DPI urgences via LLM local (qwen2.5:7b par défaut) et propose FORFAIT_URGENCE ou REQUALIFICATION_HOSPITALISATION. Retourne JSON {decision, justification, elements_pour/contre, confiance}. Bench validé 100% accuracy.', category: 'logic', needsAnchor: false, params: [
|
{ type: 't2a_decision', label: 'Décision T2A (LLM)', icon: '🧠', description: 'Analyse un DPI urgences via LLM local (qwen2.5:7b par défaut) et propose FORFAIT_URGENCE ou REQUALIFICATION_HOSPITALISATION. Retourne JSON {decision, justification, elements_pour/contre, confiance}. Bench validé 100% accuracy.', category: 'logic', needsAnchor: false, params: [
|
||||||
{ name: 'input_template', type: 'string', description: 'DPI à analyser. Supporte le templating {{var}} pour concaténer plusieurs extractions (ex: "{{texte_motif}}\\n{{texte_examens}}\\n{{texte_notes}}")' },
|
{ name: 'input_template', type: 'string', description: 'DPI à analyser. Supporte le templating {{var}} pour concaténer plusieurs extractions (ex: "{{texte_motif}}\\n{{texte_examens}}\\n{{texte_notes}}")' },
|
||||||
@@ -312,13 +325,19 @@ export interface WorkflowSummary {
|
|||||||
export interface Execution {
|
export interface Execution {
|
||||||
id: string;
|
id: string;
|
||||||
workflow_id: string;
|
workflow_id: string;
|
||||||
status: 'pending' | 'running' | 'paused' | 'completed' | 'error' | 'cancelled';
|
status: 'pending' | 'running' | 'paused' | 'paused_need_help' | 'completed' | 'error' | 'cancelled';
|
||||||
progress: number;
|
progress: number;
|
||||||
current_step_index: number;
|
current_step_index: number;
|
||||||
completed_steps: number;
|
completed_steps: number;
|
||||||
failed_steps: number;
|
failed_steps: number;
|
||||||
total_steps: number;
|
total_steps: number;
|
||||||
error_message?: string;
|
error_message?: string;
|
||||||
|
// === QW4 — Pause supervisée (renvoyés par /replay/state quand status = paused_need_help) ===
|
||||||
|
pause_reason?: string;
|
||||||
|
pause_message?: string;
|
||||||
|
safety_checks?: SafetyCheck[];
|
||||||
|
// ID du replay (utile pour appeler /replay/resume avec acknowledged_check_ids)
|
||||||
|
replay_id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Session {
|
export interface Session {
|
||||||
|
|||||||
Reference in New Issue
Block a user