This commit is contained in:
Renfei 2026-05-31 01:23:28 -04:00 committed by GitHub
commit 6e149fd49c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 62 additions and 8 deletions

View file

@ -174,14 +174,56 @@ function formatRunTimestamp(ts: number): string {
});
}
function runFailureReason(run: {
status: RoutineRun['status'];
error?: string | null;
summary?: string | null;
} | null | undefined): string | null {
// Maps known daemon-surfaced failure message prefixes onto i18n keys, so
// users running the app in a non-English locale see a translated reason
// instead of the raw English string the daemon ships to web (see e.g.
// `apps/daemon/src/server.ts` `createSseErrorPayload` callsites). We match
// on a stable prefix rather than the full string so any trailing context
// the daemon may append (e.g. `: <provider detail>`) is preserved verbatim
// after the localized prefix — losing it would hide debugging context the
// caller may rely on. Anything not in this list passes through untouched —
// untranslated failures continue to surface so behavior never regresses,
// they just stay in English until a matching i18n key is added here.
// Each entry is the *full current daemon message* (copied verbatim from
// the corresponding `createSseErrorPayload` call) paired with the i18n
// key that translates it. We match on full-string prefix so the current
// contract resolves to `t(key)` exactly; if the daemon later appends
// extra context (e.g. `: <provider detail>`), the extra text is
// preserved as a suffix on the translated message rather than dropped.
const KNOWN_DAEMON_ERROR_PREFIXES: ReadonlyArray<readonly [string, keyof Dict]> = [
[
// Source: apps/daemon/src/server.ts `AGENT_EXECUTION_FAILED` payload.
'Agent completed without producing any output. The model or provider may have returned an empty response — check the agent logs for upstream errors.',
'routines.error.agentEmptyOutput',
],
];
function localizeRoutineError(t: TranslateFn, raw: string): string {
for (const [prefix, key] of KNOWN_DAEMON_ERROR_PREFIXES) {
if (raw.startsWith(prefix)) {
// Preserve any suffix the daemon may have appended (e.g. provider
// detail after the known prefix). When the daemon emits exactly
// the prefix string today, `suffix` is empty and the result is
// just `t(key)`; nothing changes for the current contract.
const suffix = raw.slice(prefix.length);
return `${t(key)}${suffix}`;
}
}
return raw;
}
function runFailureReason(
t: TranslateFn,
run: {
status: RoutineRun['status'];
error?: string | null;
summary?: string | null;
} | null | undefined,
): string | null {
if (!run || run.status !== 'failed') return null;
const reason = (run.error || run.summary || '').trim();
return reason || null;
if (!reason) return null;
return localizeRoutineError(t, reason);
}
type FormState = {
@ -414,7 +456,7 @@ function RunHistory({
return (
<ul className="routines-history">
{runs.map((r) => {
const failureReason = runFailureReason(r);
const failureReason = runFailureReason(t, r);
return (
<li key={r.id} className="routines-history-row">
<StatusPill status={r.status} t={t} />
@ -763,7 +805,7 @@ export function RoutinesSection({ onClose }: RoutinesSectionProps) {
: t('routines.targetCreate');
const isBusy = busyId === r.id;
const isExpanded = expandedId === r.id;
const failureReason = runFailureReason(r.lastRun);
const failureReason = runFailureReason(t, r.lastRun);
return (
<li key={r.id} className={`routines-card routines-item${r.enabled ? '' : ' is-disabled'}`}>
<div className="routines-item-head">

View file

@ -967,6 +967,8 @@ export const en: Dict = {
'Delete this automation? Past runs and their projects are kept.',
'routines.errorPickProject':
'Pick a project to reuse, or switch to “Create new each run”',
'routines.error.agentEmptyOutput':
'Agent completed without producing any output. The model or provider may have returned an empty response — check the agent logs for upstream errors.',
'entry.helpAria': 'Help',
'entry.helpMenuAria': 'Help menu',
'entry.helpGetHelp': 'Get help on GitHub',

View file

@ -964,6 +964,8 @@ export const zhCN: Dict = {
'routines.status.canceled': '已取消',
'routines.confirmDelete': '删除此自动化?过往运行记录及其项目将予以保留。',
'routines.errorPickProject': '请选择要复用的项目,或切换为“每次运行新建项目”。',
'routines.error.agentEmptyOutput':
'智能体执行完成但未产生任何输出。模型或服务商可能返回了空响应——请查看智能体日志以排查上游错误。',
'entry.helpAria': '帮助',
'entry.helpMenuAria': '帮助菜单',
'entry.helpGetHelp': '在 GitHub 上获取帮助',

View file

@ -698,6 +698,8 @@ export const zhTW: Dict = {
'routines.status.canceled': '已取消',
'routines.confirmDelete': '刪除此自動化?過往執行記錄及其專案將予以保留。',
'routines.errorPickProject': '請選擇要重複使用的專案,或切換為「每次執行建立新專案」。',
'routines.error.agentEmptyOutput':
'代理執行完成但未產生任何輸出。模型或服務商可能傳回了空回應——請查看代理日誌以排查上游錯誤。',
'newproj.tabPrototype': '原型',
'newproj.tabLiveArtifact': '即時成品',

View file

@ -1279,6 +1279,12 @@ export interface Dict {
'routines.status.canceled': string;
'routines.confirmDelete': string;
'routines.errorPickProject': string;
// Routine run failure messages surfaced from the daemon. Each key
// corresponds to a well-known daemon-side error string we recognize
// and localize in `localizeRoutineError` (apps/web/src/components/
// RoutinesSection.tsx). Unknown daemon errors keep their raw string
// so untranslated failures still surface, just in English.
'routines.error.agentEmptyOutput': string;
// Bottom-of-rail help menu
'entry.helpAria': string;
'entry.helpMenuAria': string;