Files
Agent-System/web/src/lib/taskRunErrorInfo.ts
T
weichenandCursor 568408cced feat(web): degrade stage-blocked task runs to chat confirmation
Keep weak write verbs off the task shortcut path, and once-only fall back
to chat when TASK_SCOPE_UNRESOLVED or DOC_TASK_NOT_IN_ACTIVE_STAGE.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-21 08:27:49 +00:00

158 lines
5.1 KiB
TypeScript

import { fallbackErrorClass } from '../api/chatRetry';
import type { ChatErrorInfo } from '../types/workbench';
/** GET /api/v1/tasks/runs/{id} 的最小视图。兼容修复前的 PascalCase 响应。 */
export type RemoteTaskRunView = {
status?: string;
Status?: string;
error_message?: string;
ErrorMessage?: string;
workspace_id?: string;
WorkspaceID?: string;
};
export function readRemoteTaskRun(remote: RemoteTaskRunView | null | undefined): {
status: string;
errorMessage: string;
workspaceId: string;
} {
if (!remote) {
return { status: '', errorMessage: '', workspaceId: '' };
}
return {
status: remote.status || remote.Status || '',
errorMessage: (remote.error_message || remote.ErrorMessage || '').trim(),
workspaceId: remote.workspace_id || remote.WorkspaceID || '',
};
}
const fatalDenialCopy: Record<string, string> = {
DOC_TASK_DISABLED: '该文档任务未启用,当前请求未执行',
WORKFLOW_NOT_ACTIVATED: '当前项目尚未激活工作流,文档任务不会启动',
WORKFLOW_INSTANCE_CLOSED: '工作流实例已关闭,无法执行该文档任务',
DOC_DUTY_NOT_MANAGED: '该文档未配置职责,无法执行',
SESSION_FORBIDDEN: '无权访问该会话',
TASK_SCOPE_UNRESOLVED: '无法定位该文档任务的工作流阶段,请检查任务名或联系管理员',
};
/** 任务直通道失败后应降级到聊天确认引导的错误码(仅降级一次,防循环)。 */
export function isTaskStageDegradeCode(code: string | undefined | null): boolean {
return code === 'TASK_SCOPE_UNRESOLVED' || code === 'DOC_TASK_NOT_IN_ACTIVE_STAGE';
}
export function buildNetworkErrorInfo(
httpStatus?: number,
opts?: { errorCode?: string; detail?: string },
): ChatErrorInfo {
const code = opts?.errorCode || '';
const detail = opts?.detail || '';
if (code === 'DOC_BUSY' || code === 'document_busy') {
return {
userMessage: detail || '该文档正在被其他用户或项目助手编辑,当前请求未创建新任务',
code: 'DOC_BUSY',
class: 'fatal',
actions: [],
};
}
if (code === 'DOC_TASK_NOT_IN_ACTIVE_STAGE') {
return {
userMessage: detail || '该文档任务不在当前活动阶段,请先完成当前阶段工作流后再试',
code,
class: 'transient',
actions: ['retry'],
};
}
if (code === 'DOC_DUTY_FORBIDDEN') {
return {
userMessage: detail || '当前账号不是该文档任务的职责人,无法执行',
code,
class: 'fatal',
actions: ['retry'],
};
}
if (fatalDenialCopy[code]) {
const retryable = code === 'WORKFLOW_NOT_ACTIVATED' || code === 'DOC_DUTY_NOT_MANAGED' || code === 'TASK_SCOPE_UNRESOLVED';
return {
userMessage: detail || fatalDenialCopy[code],
code,
class: 'fatal',
actions: retryable ? ['retry'] : [],
};
}
if (httpStatus === 401) {
return {
userMessage: '登录已失效,请重新登录后再试',
code: 'ERR_UNAUTHENTICATED',
class: 'fatal',
actions: ['retry'],
};
}
if (httpStatus === 403) {
if (code === 'ERR_FORBIDDEN' || code === 'AUTHZ') {
return {
userMessage: detail || '无权访问该项目,请确认登录账号与项目成员身份',
code: code || 'ERR_FORBIDDEN',
class: 'fatal',
actions: ['retry'],
};
}
return {
userMessage: detail || '无权执行该操作,请确认账号权限与工作流阶段',
code: code || 'ERR_FORBIDDEN',
class: 'fatal',
actions: ['retry'],
};
}
if (httpStatus !== undefined && httpStatus >= 400 && httpStatus < 500) {
const shown = code || `HTTP_${httpStatus}`;
return {
userMessage: detail || `请求失败(${shown}),请重试或联系管理员`,
code: shown,
class: 'fatal',
actions: ['retry'],
};
}
return {
userMessage: '网络连接异常,请检查网络后重试',
code: 'NETWORK_ERROR',
class: fallbackErrorClass({ network: true }),
actions: ['retry'],
};
}
/** interrupted 且有工作区时可继续;否则只允许按原请求重开。 */
export function buildInterruptedTaskErrorInfo(errorMessage: string, workspaceId: string): ChatErrorInfo {
const message = errorMessage.trim() || '任务执行中断,可继续或重新开始';
const resumable = workspaceId.trim() !== '';
return {
userMessage: message,
code: 'TASK_INTERRUPTED',
class: resumable ? 'resumable' : 'fatal',
workspaceId: resumable ? workspaceId : undefined,
actions: resumable ? ['continue', 'restart'] : ['restart'],
};
}
/** 服务端已判定 failed:有 error_message 时展示真实原因,否则才回落到网络错误。 */
export function buildFailedTaskErrorInfo(errorMessage: string): ChatErrorInfo {
const message = errorMessage.trim();
if (!message) {
return buildNetworkErrorInfo();
}
return {
userMessage: message,
code: 'TASK_FAILED',
class: 'fatal',
actions: ['retry', 'restart'],
};
}
export function buildCancelledTaskErrorInfo(): ChatErrorInfo {
return {
userMessage: '任务已取消',
code: 'TASK_CANCELLED',
class: 'cancelled',
actions: ['restart'],
};
}