feat(ssa): finalize strict stepwise agent execution flow

Align Agent mode to strict stepwise generation and execution, add deterministic and safety hardening, and sync deployment/module documentation for Phase 5A.5/5B/5C rollout.

- implement strict stepwise execution path and dependency short-circuiting
- persist step-level errors/results and stream step_* progress events
- add agent plan params patch route and schema/migration support
- improve R sanitizer/security checks and step result rendering in workspace
- update SSA module guide and deployment change checklist

Made-with: Cursor
This commit is contained in:
2026-03-11 22:49:05 +08:00
parent d3b24bd8c3
commit 6edfad032f
19 changed files with 2105 additions and 158 deletions

View File

@@ -15,13 +15,28 @@ import {
FileText,
Play,
Ban,
Save,
} from 'lucide-react';
import { useSSAStore } from '../stores/ssaStore';
import type { AgentExecutionStatus } from '../types';
import type { AgentExecutionStatus, AgentStepStatus, DataOverviewColumn, VariableDictEntry } from '../types';
import apiClient from '@/common/api/axios';
import {
MultiVarTags,
PARAM_LABELS,
SingleVarSelect,
TOOL_CONSTRAINTS,
SINGLE_VAR_KEYS,
MULTI_VAR_KEYS,
checkMismatch,
type VarInfo,
} from './WorkflowTimeline';
import { DynamicReport } from './DynamicReport';
export interface AgentCodePanelProps {
onAction?: (action: 'confirm_plan' | 'confirm_code' | 'cancel') => void;
actionLoading?: boolean;
variableDictionary?: VariableDictEntry[];
dataOverviewColumns?: DataOverviewColumn[];
}
const STATUS_LABEL: Record<AgentExecutionStatus, string> = {
@@ -49,8 +64,24 @@ const StatusBadge: React.FC<{ status: AgentExecutionStatus }> = ({ status }) =>
);
};
export const AgentCodePanel: React.FC<AgentCodePanelProps> = ({ onAction, actionLoading }) => {
const { agentExecution, executionMode } = useSSAStore();
const STEP_STATUS_LABEL: Record<AgentStepStatus, string> = {
pending: '待执行',
coding: '生成代码中',
executing: '执行中',
completed: '已完成',
error: '失败',
skipped: '已跳过',
};
export const AgentCodePanel: React.FC<AgentCodePanelProps> = ({
onAction,
actionLoading,
variableDictionary = [],
dataOverviewColumns = [],
}) => {
const { agentExecution, executionMode, addToast } = useSSAStore();
const [paramDrafts, setParamDrafts] = useState<Record<number, Record<string, unknown>>>({});
const [savingParams, setSavingParams] = useState(false);
if (executionMode !== 'agent') return null;
@@ -69,10 +100,28 @@ export const AgentCodePanel: React.FC<AgentCodePanelProps> = ({ onAction, action
);
}
const { status, planText, planSteps: rawPlanSteps, generatedCode, partialCode, errorMessage, retryCount, durationMs } = agentExecution;
const {
status,
planText,
planSteps: rawPlanSteps,
generatedCode,
partialCode,
errorMessage,
retryCount,
durationMs,
stepResults,
currentStep,
} = agentExecution;
// 防御性:从 planText JSON 解析步骤(支持 steps / plan.steps绝不展示原始 JSON
const planSteps = React.useMemo(() => {
const planSteps = React.useMemo<Array<{
order: number;
method: string;
description: string;
rationale?: string;
toolCode?: string;
params?: Record<string, unknown>;
}> | undefined>(() => {
if (rawPlanSteps && rawPlanSteps.length > 0) return rawPlanSteps;
if (!planText) return undefined;
@@ -98,6 +147,8 @@ export const AgentCodePanel: React.FC<AgentCodePanelProps> = ({ onAction, action
method: s.method ?? '',
description: s.description ?? '',
rationale: s.rationale,
toolCode: s.toolCode || s.tool_code,
params: s.params,
}));
}
return undefined;
@@ -129,6 +180,99 @@ export const AgentCodePanel: React.FC<AgentCodePanelProps> = ({ onAction, action
const isStreamingCode = status === 'coding' && !!partialCode;
const displayCode = isStreamingCode ? partialCode : (generatedCode || partialCode);
const varsMap = React.useMemo(() => {
const map = new Map<string, VarInfo>();
for (const v of variableDictionary) {
const col = dataOverviewColumns.find(c => c.name === v.name);
map.set(v.name, {
name: v.name,
type: v.confirmedType || v.inferredType,
totalLevels: col?.totalLevels,
});
}
for (const col of dataOverviewColumns) {
if (!map.has(col.name)) {
map.set(col.name, {
name: col.name,
type: col.type,
totalLevels: col.totalLevels,
});
}
}
return map;
}, [variableDictionary, dataOverviewColumns]);
const allVars = React.useMemo(() => {
if (variableDictionary.length > 0) {
return variableDictionary
.filter(v => !v.isIdLike)
.map(v => {
const col = dataOverviewColumns.find(c => c.name === v.name);
return {
name: v.name,
type: v.confirmedType || v.inferredType,
totalLevels: col?.totalLevels,
};
});
}
return dataOverviewColumns
.filter(c => !c.isIdLike)
.map(c => ({ name: c.name, type: c.type, totalLevels: c.totalLevels }));
}, [variableDictionary, dataOverviewColumns]);
const updateDraft = (stepOrder: number, key: string, value: unknown) => {
setParamDrafts(prev => ({
...prev,
[stepOrder]: {
...(prev[stepOrder] || {}),
[key]: value,
},
}));
};
const hasDrafts = Object.keys(paramDrafts).length > 0;
const savePlanParams = async () => {
if (!agentExecution?.id || !hasDrafts) return;
const payload = {
steps: Object.entries(paramDrafts).map(([stepOrder, params]) => ({
stepOrder: Number(stepOrder),
params,
})),
};
setSavingParams(true);
try {
const resp = await apiClient.patch(`/api/v1/ssa/agent-executions/${agentExecution.id}/plan-params`, payload);
const nextReview = resp?.data?.reviewResult;
const nextSteps = Array.isArray(nextReview?.steps)
? nextReview.steps.map((s: any) => ({
order: s.order ?? 0,
method: s.method ?? '',
description: s.description ?? '',
rationale: s.rationale,
toolCode: s.toolCode || s.tool_code,
params: s.params,
}))
: planSteps;
useSSAStore.getState().updateAgentExecution({
planSteps: nextSteps,
});
setParamDrafts({});
addToast('变量参数已保存', 'success');
} catch (err: any) {
addToast(err?.response?.data?.error || err?.message || '保存变量参数失败', 'error');
throw err;
} finally {
setSavingParams(false);
}
};
const handleConfirmPlan = async () => {
if (hasDrafts) {
await savePlanParams();
}
onAction?.('confirm_plan');
};
return (
<div className="agent-code-panel">
@@ -186,6 +330,46 @@ export const AgentCodePanel: React.FC<AgentCodePanelProps> = ({ onAction, action
<span className="step-method">{s.method}</span>
<span className="step-desc">{s.description}</span>
{s.rationale && <span className="step-rationale">{s.rationale}</span>}
{status === 'plan_pending' && s.params && Object.keys(s.params).length > 0 && (
<div style={{ marginTop: 8, display: 'grid', gap: 8 }}>
{Object.entries(s.params).map(([k, raw]) => {
const toolCode = s.toolCode || '';
const rule = TOOL_CONSTRAINTS[toolCode]?.[k];
const edited = (paramDrafts[s.order] || {})[k];
const value = edited !== undefined ? edited : raw;
const isSingle = SINGLE_VAR_KEYS.has(k);
const isMulti = MULTI_VAR_KEYS.has(k);
const mismatch = rule && typeof value === 'string'
? checkMismatch(value, rule, varsMap)
: null;
return (
<div key={k}>
<div className="step-rationale" style={{ marginBottom: 4 }}>{PARAM_LABELS[k] || k}</div>
{isSingle ? (
<SingleVarSelect
value={typeof value === 'string' ? value : null}
constraint={rule}
varsMap={varsMap}
allVars={allVars}
onChange={(v) => updateDraft(s.order, k, v)}
/>
) : isMulti ? (
<MultiVarTags
values={Array.isArray(value) ? value.map(String) : []}
constraint={rule}
varsMap={varsMap}
allVars={allVars}
onChange={(v) => updateDraft(s.order, k, v)}
/>
) : (
<span className="step-rationale">{String(value ?? '—')}</span>
)}
{mismatch && <span className="step-rationale" style={{ color: '#dc2626' }}>{mismatch}</span>}
</div>
);
})}
</div>
)}
</div>
</div>
))}
@@ -200,10 +384,21 @@ export const AgentCodePanel: React.FC<AgentCodePanelProps> = ({ onAction, action
{/* 计划确认操作按钮 */}
{status === 'plan_pending' && onAction && (
<div className="agent-action-bar">
{hasDrafts && (
<button
className="agent-action-btn secondary"
onClick={savePlanParams}
disabled={actionLoading || savingParams}
title="保存变量修改"
>
{savingParams ? <Loader2 size={14} className="spin" /> : <Save size={14} />}
</button>
)}
<button
className="agent-action-btn primary"
onClick={() => onAction('confirm_plan')}
disabled={actionLoading}
onClick={handleConfirmPlan}
disabled={actionLoading || savingParams}
>
{actionLoading ? <Loader2 size={14} className="spin" /> : <CheckCircle size={14} />}
@@ -221,8 +416,49 @@ export const AgentCodePanel: React.FC<AgentCodePanelProps> = ({ onAction, action
</div>
)}
{/* Step 2: R 代码 */}
{(displayCode || status === 'coding') && (
{/* Step-by-step 执行状态Phase 5B */}
{stepResults && stepResults.length > 0 && (
<div className="agent-section">
<div className="agent-section-title">
<Sparkles size={13} />
<span></span>
</div>
<div className="agent-plan-steps">
{stepResults
.slice()
.sort((a, b) => a.stepOrder - b.stepOrder)
.map((s, i) => (
<div key={i} className="plan-step-item">
<span className="step-num">{s.stepOrder}</span>
<div className="plan-step-body">
<span className="step-method">{s.method || `Step ${s.stepOrder}`}</span>
<span className="step-desc">
{STEP_STATUS_LABEL[s.status]}
{currentStep === s.stepOrder && ['coding', 'executing'].includes(s.status) ? ' · 当前步骤' : ''}
{typeof s.durationMs === 'number' ? ` · ${(s.durationMs / 1000).toFixed(1)}s` : ''}
</span>
{!!s.errorMessage && <span className="step-rationale">{s.errorMessage}</span>}
{s.status === 'completed' && s.reportBlocks && s.reportBlocks.length > 0 && (
<div style={{ marginTop: 8 }}>
<details>
<summary style={{ cursor: 'pointer', color: '#93c5fd' }}>
{s.reportBlocks.length}
</summary>
<div style={{ marginTop: 8 }}>
<DynamicReport blocks={s.reportBlocks} />
</div>
</details>
</div>
)}
</div>
</div>
))}
</div>
</div>
)}
{/* Step 2: R 代码(严格分步模式下展示首步预览;后续步骤执行时逐步生成) */}
{(displayCode || ['coding', 'code_pending', 'executing', 'completed', 'error'].includes(status)) && (
<div className="agent-section">
<div className="agent-section-title">
<Code size={13} />
@@ -234,6 +470,11 @@ export const AgentCodePanel: React.FC<AgentCodePanelProps> = ({ onAction, action
<div className="agent-code-body">
{displayCode ? (
<pre className={isStreamingCode ? 'streaming' : ''}>{displayCode}</pre>
) : status === 'code_pending' ? (
<div className="agent-code-loading">
<Sparkles size={16} />
<span></span>
</div>
) : (
<div className="agent-code-loading">
<Loader2 size={16} className="spin" />

View File

@@ -31,6 +31,7 @@ import { AgentCodePanel } from './AgentCodePanel';
import { DynamicReport } from './DynamicReport';
import { exportBlocksToWord } from '../utils/exportBlocksToWord';
import apiClient from '@/common/api/axios';
import type { VariableDictEntry, DataOverviewColumn } from '../types';
const stepHasResult = (s: WorkflowStepResult) =>
(s.status === 'success' || s.status === 'warning') && s.result;
@@ -340,6 +341,8 @@ export const SSAWorkspacePane: React.FC = () => {
<AgentCodePanel
onAction={handleAgentAction}
actionLoading={agentActionLoading}
variableDictionary={variableDictionary as VariableDictEntry[]}
dataOverviewColumns={dataOverviewColumns as DataOverviewColumn[]}
/>
{/* Agent 模式的报告输出复用 DynamicReport */}
{agentExecution?.status === 'completed' && agentExecution.reportBlocks && agentExecution.reportBlocks.length > 0 && (

View File

@@ -30,7 +30,7 @@ import type {
// ────────────────────────── Param constraints ──────────────────────────
interface ParamConstraint {
export interface ParamConstraint {
paramType: 'single' | 'multi';
requiredType: 'categorical' | 'numeric' | 'any';
minLevels?: number;
@@ -38,9 +38,9 @@ interface ParamConstraint {
hint: string;
}
type ToolConstraints = Record<string, Record<string, ParamConstraint>>;
export type ToolConstraints = Record<string, Record<string, ParamConstraint>>;
const TOOL_CONSTRAINTS: ToolConstraints = {
export const TOOL_CONSTRAINTS: ToolConstraints = {
ST_DESCRIPTIVE: {
variables: { paramType: 'multi', requiredType: 'any', hint: '选择需要描述的变量' },
group_var: { paramType: 'single', requiredType: 'categorical', hint: '分组变量(可选)' },
@@ -93,25 +93,25 @@ const TOOL_CONSTRAINTS: ToolConstraints = {
},
};
const SINGLE_VAR_KEYS = new Set([
export const SINGLE_VAR_KEYS = new Set([
'group_var', 'outcome_var', 'value_var', 'var_x', 'var_y',
'before_var', 'after_var', 'var1', 'var2',
]);
const MULTI_VAR_KEYS = new Set([
export const MULTI_VAR_KEYS = new Set([
'analyze_vars', 'predictors', 'variables', 'confounders',
]);
function isVariableParam(key: string): boolean {
export function isVariableParam(key: string): boolean {
return SINGLE_VAR_KEYS.has(key) || MULTI_VAR_KEYS.has(key);
}
interface VarInfo {
export interface VarInfo {
name: string;
type: string;
totalLevels?: number;
}
function checkMismatch(
export function checkMismatch(
varName: string,
constraint: ParamConstraint,
varsMap: Map<string, VarInfo>
@@ -202,7 +202,7 @@ const formatValue = (value: unknown): string => {
return s.length > 50 ? s.slice(0, 47) + '…' : s;
};
const PARAM_LABELS: Record<string, string> = {
export const PARAM_LABELS: Record<string, string> = {
variables: '分析变量',
outcome_var: '结局变量 (Y)',
predictors: '自变量 (X)',
@@ -231,7 +231,7 @@ interface SingleVarSelectProps {
onChange: (v: string | null) => void;
}
const SingleVarSelect: React.FC<SingleVarSelectProps> = ({ value, constraint, varsMap, allVars, onChange }) => {
export const SingleVarSelect: React.FC<SingleVarSelectProps> = ({ value, constraint, varsMap, allVars, onChange }) => {
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
@@ -318,7 +318,7 @@ interface MultiVarTagsProps {
onChange: (v: string[]) => void;
}
const MultiVarTags: React.FC<MultiVarTagsProps> = ({ values, constraint, varsMap, allVars, onChange }) => {
export const MultiVarTags: React.FC<MultiVarTagsProps> = ({ values, constraint, varsMap, allVars, onChange }) => {
const [showDropdown, setShowDropdown] = useState(false);
const ref = useRef<HTMLDivElement>(null);

View File

@@ -17,7 +17,7 @@ import { useState, useCallback, useRef } from 'react';
import { getAccessToken, isTokenExpired, refreshAccessToken } from '@/framework/auth/api';
import { useSSAStore } from '../stores/ssaStore';
import type { AskUserEventData, AskUserResponseData } from '../components/AskUserCard';
import type { WorkflowPlan } from '../types';
import type { WorkflowPlan, AgentStepResult } from '../types';
// ────────────────────────────────────────────
// Types
@@ -229,7 +229,19 @@ export function useSSAChat(): UseSSAChatReturn {
setIntentMeta(null);
setPendingQuestion(null);
const isAgentAction = !!metadata?.agentAction;
const { executionMode, agentExecution } = useSSAStore.getState();
const isAgentInlineInstruction =
executionMode === 'agent'
&& !!agentExecution
&& (agentExecution.status === 'plan_pending' || agentExecution.status === 'code_pending')
&& !metadata?.agentAction
&& !metadata?.askUserResponse;
const finalMetadata = isAgentInlineInstruction
? { ...(metadata || {}), agentInlineInstruction: true }
: metadata;
const isAgentAction = !!finalMetadata?.agentAction || isAgentInlineInstruction;
const assistantMsgId = crypto.randomUUID();
const assistantPlaceholder: ChatMessage = {
@@ -254,7 +266,8 @@ export function useSSAChat(): UseSSAChatReturn {
setChatMessages(prev => [...prev, userMsg, assistantPlaceholder]);
}
abortRef.current = new AbortController();
const controller = new AbortController();
abortRef.current = controller;
let fullContent = '';
let fullThinking = '';
@@ -267,8 +280,8 @@ export function useSSAChat(): UseSSAChatReturn {
Accept: 'text/event-stream',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ content, ...(metadata ? { metadata } : {}) }),
signal: abortRef.current.signal,
body: JSON.stringify({ content, ...(finalMetadata ? { metadata: finalMetadata } : {}) }),
signal: controller.signal,
});
if (!response.ok) {
@@ -359,10 +372,115 @@ export function useSSAChat(): UseSSAChatReturn {
if (parsed.type === 'agent_plan_ready') {
const { updateAgentExecution } = useSSAStore.getState();
const initialStepResults: AgentStepResult[] = Array.isArray(parsed.plan?.steps)
? parsed.plan.steps.map((s: any) => ({
stepOrder: s.order || 0,
method: s.method || '',
status: 'pending',
retryCount: 0,
}))
: [];
updateAgentExecution({
planText: parsed.planText,
planSteps: parsed.plan?.steps,
status: 'plan_pending',
stepResults: initialStepResults.length > 0 ? initialStepResults : undefined,
currentStep: initialStepResults.length > 0 ? initialStepResults[0].stepOrder : undefined,
});
continue;
}
const patchStepResult = (stepOrder: number, patch: Partial<AgentStepResult>) => {
const { agentExecution, updateAgentExecution } = useSSAStore.getState();
const existing = agentExecution?.stepResults || [];
const idx = existing.findIndex(s => s.stepOrder === stepOrder);
let next: AgentStepResult[];
if (idx >= 0) {
next = existing.map((s, i) => (i === idx ? { ...s, ...patch } : s));
} else {
next = [
...existing,
{
stepOrder,
method: '',
status: 'pending',
retryCount: 0,
...patch,
} as AgentStepResult,
].sort((a, b) => a.stepOrder - b.stepOrder);
}
updateAgentExecution({ stepResults: next, currentStep: stepOrder });
};
if (parsed.type === 'step_coding') {
patchStepResult(parsed.stepOrder || 0, {
status: 'coding',
partialCode: parsed.partialCode,
retryCount: parsed.retryCount || 0,
});
const { updateAgentExecution } = useSSAStore.getState();
updateAgentExecution({
partialCode: parsed.partialCode,
status: 'coding',
});
continue;
}
if (parsed.type === 'step_code_ready') {
patchStepResult(parsed.stepOrder || 0, {
status: 'coding',
code: parsed.code,
partialCode: undefined,
});
const { updateAgentExecution } = useSSAStore.getState();
updateAgentExecution({
generatedCode: parsed.code,
partialCode: undefined,
status: 'coding',
});
continue;
}
if (parsed.type === 'step_executing') {
patchStepResult(parsed.stepOrder || 0, { status: 'executing' });
const { updateAgentExecution } = useSSAStore.getState();
updateAgentExecution({ status: 'executing' });
continue;
}
if (parsed.type === 'step_result') {
patchStepResult(parsed.stepOrder || 0, {
status: 'completed',
reportBlocks: parsed.reportBlocks,
durationMs: parsed.durationMs,
errorMessage: undefined,
});
continue;
}
if (parsed.type === 'step_error') {
patchStepResult(parsed.stepOrder || 0, {
status: parsed.willRetry ? 'coding' : 'error',
errorMessage: parsed.message,
retryCount: parsed.retryCount || 0,
});
continue;
}
if (parsed.type === 'step_skipped') {
patchStepResult(parsed.stepOrder || 0, {
status: 'skipped',
errorMessage: parsed.reason,
});
continue;
}
if (parsed.type === 'pipeline_aborted') {
const { updateAgentExecution } = useSSAStore.getState();
updateAgentExecution({
status: 'error',
errorMessage: parsed.error || '执行已终止',
currentStep: parsed.stepOrder,
});
continue;
}
@@ -379,9 +497,9 @@ export function useSSAChat(): UseSSAChatReturn {
if (parsed.type === 'code_generated') {
const { updateAgentExecution } = useSSAStore.getState();
updateAgentExecution({
generatedCode: parsed.code,
generatedCode: parsed.code || undefined,
partialCode: undefined,
status: parsed.code ? 'code_pending' : 'coding',
status: 'code_pending',
});
continue;
}
@@ -399,6 +517,7 @@ export function useSSAChat(): UseSSAChatReturn {
generatedCode: parsed.code || curExec?.generatedCode,
status: 'completed',
durationMs: parsed.durationMs,
stepResults: parsed.stepResults || curExec?.stepResults,
});
// 在对话中插入可点击的结果卡片
@@ -503,7 +622,9 @@ export function useSSAChat(): UseSSAChatReturn {
: m
));
setIsGenerating(false);
abortRef.current = null;
if (abortRef.current === controller) {
abortRef.current = null;
}
await new Promise(r => setTimeout(r, delay));
return sendChatMessage(sessionId, content, metadata);
}
@@ -521,7 +642,9 @@ export function useSSAChat(): UseSSAChatReturn {
setIsGenerating(false);
setStreamingContent('');
setThinkingContent('');
abortRef.current = null;
if (abortRef.current === controller) {
abortRef.current = null;
}
}
}, []);
@@ -533,7 +656,7 @@ export function useSSAChat(): UseSSAChatReturn {
*/
const executeAgentAction = useCallback(async (sessionId: string, action: AgentActionType) => {
const AUDIT_MESSAGES: Record<AgentActionType, string> = {
confirm_plan: '✅ 方案已确认,正在生成 R 代码...',
confirm_plan: '✅ 方案已确认,已进入执行确认(执行时将分步生成代码...',
confirm_code: '✅ 代码已确认R 引擎正在执行...',
cancel: '❌ 已取消当前分析',
};

View File

@@ -20,6 +20,7 @@ import type {
FiveSectionReport,
VariableDetailData,
AgentExecutionRecord,
AgentStepResult,
} from '../types';
type ArtifactPane = 'empty' | 'sap' | 'execution' | 'result';
@@ -250,6 +251,8 @@ export const useSSAStore = create<SSAState>((set) => ({
method: s.method,
description: s.description,
rationale: s.rationale,
toolCode: s.toolCode || s.tool_code,
params: s.params,
}));
}
planMeta = { title: structured.title, designType: structured.designType };
@@ -261,7 +264,12 @@ export const useSSAStore = create<SSAState>((set) => ({
const arr = Array.isArray(parsed?.steps) ? parsed.steps : parsed?.plan?.steps;
if (Array.isArray(arr)) {
planSteps = arr.map((s: any) => ({
order: s.order, method: s.method, description: s.description, rationale: s.rationale,
order: s.order,
method: s.method,
description: s.description,
rationale: s.rationale,
toolCode: s.toolCode || s.tool_code,
params: s.params,
}));
}
if (!planMeta) planMeta = { title: parsed.title, designType: parsed.designType };
@@ -278,6 +286,8 @@ export const useSSAStore = create<SSAState>((set) => ({
reportBlocks: e.reportBlocks,
retryCount: e.retryCount || 0,
status: e.status,
stepResults: Array.isArray(e.stepResults) ? (e.stepResults as AgentStepResult[]) : undefined,
currentStep: typeof e.currentStep === 'number' ? e.currentStep : undefined,
errorMessage: e.errorMessage,
durationMs: e.durationMs,
createdAt: e.createdAt,

View File

@@ -377,6 +377,7 @@ export type SSEMessageType =
| 'workflow_complete' | 'workflow_error'
| 'qper_status' | 'reflection_complete'
| 'agent_planning' | 'agent_plan_ready'
| 'step_coding' | 'step_code_ready' | 'step_executing' | 'step_result' | 'step_skipped' | 'pipeline_aborted'
| 'code_generating' | 'code_generated'
| 'code_executing' | 'code_result' | 'code_error' | 'code_retry';
@@ -386,17 +387,40 @@ export type AgentExecutionStatus =
| 'coding' | 'code_pending'
| 'executing' | 'completed' | 'error';
export type AgentStepStatus = 'pending' | 'coding' | 'executing' | 'completed' | 'error' | 'skipped';
export interface AgentStepResult {
stepOrder: number;
method: string;
status: AgentStepStatus;
code?: string;
partialCode?: string;
reportBlocks?: ReportBlock[];
errorMessage?: string;
retryCount: number;
durationMs?: number;
}
export interface AgentExecutionRecord {
id: string;
sessionId: string;
query: string;
planText?: string;
planSteps?: Array<{ order: number; method: string; description: string; rationale?: string }>;
planSteps?: Array<{
order: number;
method: string;
description: string;
rationale?: string;
toolCode?: string;
params?: Record<string, unknown>;
}>;
generatedCode?: string;
partialCode?: string;
reportBlocks?: ReportBlock[];
retryCount: number;
status: AgentExecutionStatus;
stepResults?: AgentStepResult[];
currentStep?: number;
errorMessage?: string;
durationMs?: number;
createdAt?: string;

File diff suppressed because one or more lines are too long