Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions app/desktop/src/platform/useDesktopEdgeEvents.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { normalizeEdgeEventsToTranscript } from '@shared/transcript';
import { eventRunId } from '@shared/transcript/edgeEventEvidence';
import { getAgentActivityStore, type AgentActivityStatus } from '@shared/transcript/agentActivity';
import type { EventEnvelope } from '@shared/events';
import type { TranscriptBlock } from '@shared/transcript';
Expand Down Expand Up @@ -170,10 +171,6 @@ function eventThreadId(event: EventEnvelope): string | undefined {
return stringField(event.payload.threadId) ?? stringField(event.scope.threadId);
}

function eventRunId(event: EventEnvelope): string | undefined {
return stringField(event.payload.runId) ?? stringField(event.scope.runId);
}

function eventTimestampMs(event: EventEnvelope): number {
const parsed = Date.parse(event.sentAt);
return Number.isFinite(parsed) ? parsed : Number.POSITIVE_INFINITY;
Expand Down
7 changes: 1 addition & 6 deletions app/desktop/src/platform/useDesktopWorkbenchModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
type WorkbenchDataMode,
} from '@shared/demo';
import { normalizeThreadItemsToTranscript } from '@shared/transcript';
import { errorMessage } from '@shared/errors';
import { appDateLocaleTag } from '@shared/i18n/locale';
import type { TFunction } from 'i18next';
import { getI18n } from 'react-i18next';
Expand Down Expand Up @@ -613,9 +614,3 @@ function workspaceProjectToProjectInfo(
feed: [],
};
}

function errorMessage(error: unknown, fallback: string): string {
if (error instanceof Error && error.message.trim()) return error.message;
if (typeof error === 'string' && error.trim()) return error;
return fallback;
}
19 changes: 19 additions & 0 deletions app/shared/src/errors.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
import { describe, expect, it } from 'vitest';
import {
AppError,
errorMessage,
isErrorResponse,
isTurnInProgressError,
parseError,
type ErrorBody,
} from './errors';

describe('errorMessage', () => {
it('prefers Error.message', () => {
expect(errorMessage(new Error('boom'), 'fallback')).toBe('boom');
});

it('keeps a non-blank thrown string', () => {
expect(errorMessage('plain failure', 'fallback')).toBe('plain failure');
});

it('falls back for blank, non-Error and empty values', () => {
expect(errorMessage('', 'fallback')).toBe('fallback');
expect(errorMessage(' ', 'fallback')).toBe('fallback');
expect(errorMessage(new Error(' '), 'fallback')).toBe('fallback');
expect(errorMessage({ message: 'nope' }, 'fallback')).toBe('fallback');
expect(errorMessage(undefined, 'fallback')).toBe('fallback');
});
});

describe('isTurnInProgressError', () => {
const turnBody: ErrorBody = {
error: { code: 'turn_in_progress', message: 'agent instance already has an active task' },
Expand Down
14 changes: 14 additions & 0 deletions app/shared/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@ export function isTurnInProgressError(error: unknown): boolean {
return error instanceof AppError && error.status === 409 && error.code === 'turn_in_progress';
}

/**
* Best-effort user-facing message for a caught value.
*
* Every platform had its own copy of this and one of them silently dropped the
* `string` case, so a thrown string surfaced as the generic fallback. This is
* the single definition: `Error.message`, then a non-blank thrown string, then
* the caller's fallback.
*/
export function errorMessage(error: unknown, fallback: string): string {
if (error instanceof Error && error.message.trim()) return error.message;
if (typeof error === 'string' && error.trim()) return error;
return fallback;
}

export async function parseError(response: Response): Promise<AppError> {
try {
const body = await response.json();
Expand Down
6 changes: 1 addition & 5 deletions app/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useCallback, useMemo, useState, useSyncExternalStore } from 'react';
import { useTranslation } from 'react-i18next';
import { AgentHubWorkbench } from '@agenthub/workbench';
import { fetchAllPages } from '@shared/hub/paginate';
import { errorMessage } from '@shared/errors';
import { webQueryKeys } from '@shared/stores/queryKeys';
import { CHATVIEW_I18N_NAMESPACE } from '@shared/chatview/i18n/resources';
import type { AgentConfig, ContactMember, ProjectDraft, ProjectInfo, SkillMarketItem, MCPMarketItem } from '@agenthub/workbench';
Expand Down Expand Up @@ -421,11 +422,6 @@ function WebWorkbenchRoot() {
);
}

function errorMessage(error: unknown, fallback: string): string {
if (error instanceof Error && error.message.trim()) return error.message;
return fallback;
}

function normalizeHubSkillToMarketItem(raw: Record<string, unknown>): SkillMarketItem {
const item: SkillMarketItem = {
id: String(raw.id ?? ''),
Expand Down
10 changes: 0 additions & 10 deletions app/web/src/platform/useWebWorkbenchModel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,6 @@ import {
useWebSessionAutoMarkRead,
} from './useWebWorkbenchModel';
import { webTranscript } from './webPlatform';
import { errorMessage } from './webWorkbenchError';

describe('webWorkbenchError', () => {
it('prefers Error.message, then string, then fallback', () => {
expect(errorMessage(new Error('boom'), 'fallback')).toBe('boom');
expect(errorMessage(' string-error ', 'fallback')).toBe(' string-error ');
expect(errorMessage(null, 'fallback')).toBe('fallback');
expect(errorMessage(new Error(' '), 'fallback')).toBe('fallback');
});
});

describe('useWebWorkbenchModel helpers', () => {
it('maps Hub contacts into shared workbench contacts', () => {
Expand Down
2 changes: 1 addition & 1 deletion app/web/src/platform/useWebWorkbenchModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ import {
resolveWebTranscriptMessages,
resolveWebWorkbenchTranscript,
} from './webWorkbenchTranscript';
import { errorMessage } from './webWorkbenchError';
import { errorMessage } from '@shared/errors';

const hubClient = createHubClient({ getToken: getAccessToken });

Expand Down
5 changes: 0 additions & 5 deletions app/web/src/platform/webWorkbenchError.ts

This file was deleted.

2 changes: 1 addition & 1 deletion app/web/src/platform/webWorkbenchExecutionTargets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { WorkbenchDataMode } from '@shared/demo';
import { isWorkbenchRealDataMode } from '@shared/demo';
import type { TranscriptBlock } from '@shared/transcript';
import type { ExecutionTargetInventoryItem } from '@/api/executionTargetQueries';
import { errorMessage } from './webWorkbenchError';
import { errorMessage } from '@shared/errors';

export type WebExecutionTargetStatusState =
| 'hidden'
Expand Down
6 changes: 0 additions & 6 deletions app/web/src/platform/webWorkbenchHelpers.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import { describe, expect, it } from 'vitest';
import { errorMessage } from './webWorkbenchError';
import { projectDraftToHubRequest } from './webWorkbenchProjects';
import { executionTargetLabel, resolveWebExecutionTargetStatus } from './webWorkbenchExecutionTargets';

describe('webWorkbench helper seam', () => {
it('errorMessage covers non-Error objects', () => {
expect(errorMessage({ message: 'nope' }, 'fallback')).toBe('fallback');
expect(errorMessage('', 'fallback')).toBe('fallback');
});

it('projectDraftToHubRequest falls back to default name', () => {
expect(projectDraftToHubRequest({ name: ' ', description: 'x' })).toEqual({
name: '未命名项目',
Expand Down
2 changes: 1 addition & 1 deletion app/web/src/platform/webWorkbenchProjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type {
WorkspaceProjectThread,
WorkspaceProjectThreadMessage,
} from '@/api/hubClient';
import { errorMessage } from './webWorkbenchError';
import { errorMessage } from '@shared/errors';

export function resolveWebWorkbenchProjects(
projects: WorkspaceProject[] | undefined,
Expand Down
2 changes: 1 addition & 1 deletion app/web/src/platform/webWorkbenchTranscript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from '@shared/transcript';
import type { FileDiff } from '@shared/types/chat';
import { webHubEmptyTranscript } from './webPlatform';
import { errorMessage } from './webWorkbenchError';
import { errorMessage } from '@shared/errors';

export function resolveWebRuntimeEvidence(transcript: TranscriptBlock[]): RuntimeEvidenceSnapshot {
const runId = resolveCurrentTranscriptRunId(transcript);
Expand Down
Loading