Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
20be47b
refactor(frontend): 删掉 34 个全仓 0 引用导出的 export 修饰符(18 文件,实现保留)——按 #2274…
DeliciousBuding Sep 3, 2026
1fa6706
refactor(frontend): 删掉 7 文件 13 个全仓 0 引用的真死导出符号(含实现与其描述性注释)——C-2 同族复核:…
DeliciousBuding Sep 3, 2026
ca6bdf5
docs(shared): 修掉 5 处过期/不实注释(D-1/D-3/D-4/D-5,均有 git log/grep 证据)——diff…
DeliciousBuding Sep 3, 2026
efc7845
docs(mobile-rn): 去掉指向仓外一次性 lane 工件的裸 BLOCKED.md 指针(D-9)——mobilePlatfo…
DeliciousBuding Sep 3, 2026
677327b
test(workbench): 修正 14 处过期路径字面量 app/shared/src/workbench/ → app/workb…
DeliciousBuding Sep 3, 2026
9f200ac
docs(shared): 补掉 dc7f6bbe 漏改的一处——HubWSGapPayload 的头注仍点名同批删掉的 HUB_WS_G…
DeliciousBuding Sep 3, 2026
e78c440
refactor(frontend): 清理写集内 102 处死 export 修饰符(全仓除定义外 0 引用)
DeliciousBuding Sep 3, 2026
650a18f
docs(desktop): hubEventBridge 头注指向真实 Edge 事件入口,断链修复 (#2246 / D-2)
DeliciousBuding Sep 3, 2026
6136ae1
fix(desktop): 登录页错误处理回灌 web 的 i18n 规则,不再直出 err.message (#2256 D-P1-4)
DeliciousBuding Sep 3, 2026
61e4fb9
refactor(web): hubClient.ts 删除 shared DTO 的本地副本,不再遮蔽 SSOT 同名别名 (#2256…
DeliciousBuding Sep 3, 2026
1d70cc7
fix(frontend): 执行目标健康态白名单收敛——web 补 registered、两端去掉 | string 吸收、deskto…
DeliciousBuding Sep 3, 2026
59f0772
fix(web): 执行目标清单收敛到 desktop 的 50×10 cursor 翻页,取满 500 并如实报 hasMore (#2…
DeliciousBuding Sep 3, 2026
7759883
chore(i18n): 删掉 web 的 agents.newDefault 键(en/zh 各一行)——cd3b7a4b 删掉的 cr…
DeliciousBuding Sep 3, 2026
7375132
chore(frontend): 修掉本批自己引入的 2 处 EOF 空行——e35c7387 删掉 mobile-rn theme/mo…
DeliciousBuding Sep 3, 2026
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
2 changes: 1 addition & 1 deletion app/desktop/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@
);
}

export interface DesktopWorkbenchAppProps {
interface DesktopWorkbenchAppProps {
onLogout?: (() => void) | undefined;
}

Expand Down Expand Up @@ -263,7 +263,7 @@

const documents = useMemo<DocRow[] | undefined>(
() => (liveEdgeEnabled && documentListData?.items ? documentListData.items.map((doc) => hubDocToDocRow(doc, tIm)) : undefined),
[liveEdgeEnabled, documentListData],

Check warning on line 266 in app/desktop/src/App.tsx

View workflow job for this annotation

GitHub Actions / frontend-desktop (2)

React Hook useMemo has a missing dependency: 'tIm'. Either include it or remove the dependency array

Check warning on line 266 in app/desktop/src/App.tsx

View workflow job for this annotation

GitHub Actions / frontend-desktop (1)

React Hook useMemo has a missing dependency: 'tIm'. Either include it or remove the dependency array
);
const documentsError = useMemo(
() => (liveEdgeEnabled && documentListError
Expand Down
23 changes: 18 additions & 5 deletions app/desktop/src/__tests__/LoginForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,27 +65,40 @@ describe('LoginForm', () => {
});
});

it('shows TokenDance errors from the auth hook', async () => {
// #2154 P2-10 (backported from web LoginForm): the banner must carry localized
// copy, never err.message. The test i18next instance echoes keys / honors
// defaultValue, so the assertions pin the resolved key instead of the raw
// transport string.
it('falls back to the localized generic failure instead of echoing err.message', async () => {
mockLoginWithTokenDance.mockRejectedValueOnce(new Error('Hub login unavailable'));
renderForm();

fireEvent.click(screen.getByRole('button', { name: 'auth.tokenDanceLogin' }));

await waitFor(() => {
expect(screen.getByRole('alert')).toHaveTextContent('Hub login unavailable');
const alert = screen.getByRole('alert');
expect(alert).toHaveTextContent('auth.error.oidc.default');
expect(alert.textContent).not.toContain('Hub login unavailable');
});
expect(screen.queryByRole('status')).not.toBeInTheDocument();
});

it('falls back to the localized unavailable error when rejection has no message', async () => {
mockLoginWithTokenDance.mockRejectedValueOnce({});
it('resolves known OidcError codes through auth.error.oidc.<code>', async () => {
const { OidcError } = await import('@/api/hubAuth');
mockLoginWithTokenDance.mockRejectedValueOnce(
new OidcError('startFailed', 'Failed to start OIDC login: fetch failed', 'fetch failed'),
);
renderForm();

fireEvent.click(screen.getByRole('button', { name: 'auth.tokenDanceLogin' }));

await waitFor(() => {
expect(screen.getByRole('alert')).toHaveTextContent('auth.error.tokenDanceUnavailable');
const alert = screen.getByRole('alert');
expect(alert).toHaveTextContent('auth.error.oidc');
expect(alert.textContent).not.toContain('fetch failed');
expect(alert.textContent).not.toContain('Failed to start OIDC login');
});
expect(screen.queryByRole('status')).not.toBeInTheDocument();
});

it('calls onSuccess and renders nothing when a Hub user is already authenticated', () => {
Expand Down
29 changes: 0 additions & 29 deletions app/desktop/src/api/agentProfileQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
}

/** Map AgentConfig (from AgentsPage edit panel) to Edge creation payload. */
type AppTranslate = (key: string, options?: any) => string;

Check failure on line 61 in app/desktop/src/api/agentProfileQueries.ts

View workflow job for this annotation

GitHub Actions / frontend-desktop (2)

Unexpected any. Specify a different type

Check failure on line 61 in app/desktop/src/api/agentProfileQueries.ts

View workflow job for this annotation

GitHub Actions / frontend-desktop (1)

Unexpected any. Specify a different type

function agentConfigToEdgeProfile(agent: AgentConfig, t?: AppTranslate): Record<string, unknown> {
const modelParts = (agent.model ?? '').split('/').map((s) => s.trim()).filter(Boolean);
Expand Down Expand Up @@ -149,37 +149,8 @@
});
}

export function useHubCreateAgentProfile() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: import('@/api/hubClient').CreateAgentProfileRequest) =>
getHubClient().createAgentProfile(data),
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['hub', 'agent-profiles'] });
},
});
}

export function useHubUpdateAgentProfile() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: import('@/api/hubClient').UpdateAgentProfileRequest }) =>
getHubClient().updateAgentProfile(id, data),
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['hub', 'agent-profiles'] });
},
});
}

export function useHubDeleteAgentProfile() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => getHubClient().deleteAgentProfile(id),
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['hub', 'agent-profiles'] });
},
});
}

/** Safely parse a JSON string field from Hub, returning undefined on failure. */
function safeJsonParse<T>(value: string | undefined | null): T | undefined {
Expand Down
24 changes: 4 additions & 20 deletions app/desktop/src/api/edgeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,23 +257,7 @@ export async function fetchThreadPins(threadId: string): Promise<ListResponse<Th
return safeParse<ListResponse<ThreadPinInfo>>(listResponseSchema(ThreadPinInfoSchema), unwrapEdgeResponse(await res.json()), 'threadPins');
}

export async function pinThreadItem(threadId: string, itemId: string, pinnedBy?: string): Promise<ThreadPinInfo> {
const res = await edgeFetch(`${BASE}/v1/threads/${encodeURIComponent(threadId)}/pins`, {
method: 'POST',
...edgeDevRequestInit({}, { 'Content-Type': 'application/json' }),
body: JSON.stringify({ itemId, pinnedBy }),
});
if (!res.ok) throw await parseError(res);
return safeParse<ThreadPinInfo>(ThreadPinInfoSchema, unwrapEdgeResponse(await res.json()), 'pinThreadItem');
}

export async function deleteThreadPin(threadId: string, itemId: string): Promise<void> {
const res = await edgeFetch(`${BASE}/v1/threads/${encodeURIComponent(threadId)}/pins?itemId=${encodeURIComponent(itemId)}`, {
method: 'DELETE',
...edgeDevRequestInit(),
});
if (!res.ok) throw await parseError(res);
}

export async function fetchRuns(projectId?: string, threadId?: string): Promise<ListResponse<RunInfo>> {
const params = new URLSearchParams();
Expand Down Expand Up @@ -312,27 +296,27 @@ export async function fetchRunDiff(runId: string): Promise<RunDiff> {
return safeParse<RunDiff>(RunDiffSchema, unwrapEdgeResponse(await res.json()), 'runDiff');
}

export interface ApplyRunDiffRequest {
interface ApplyRunDiffRequest {
filePath: string;
hunkIndex: number;
accepted: boolean;
workDir: string;
}

export interface ApplyRunDiffResponse {
interface ApplyRunDiffResponse {
runId: string;
filePath: string;
hunkIndex: number;
accepted: boolean;
applied: boolean;
}

export interface ApplyAllRunDiffsRequest {
interface ApplyAllRunDiffsRequest {
decisions: Array<Pick<ApplyRunDiffRequest, 'filePath' | 'hunkIndex' | 'accepted'>>;
workDir: string;
}

export interface ApplyAllRunDiffsResponse {
interface ApplyAllRunDiffsResponse {
runId: string;
applied: number;
}
Expand Down
2 changes: 1 addition & 1 deletion app/desktop/src/api/eventClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export type {
} from '@agenthub/shared';
export type { EventEnvelope } from '@shared/events';

export interface EventStreamLegacyOptions extends EventStreamOptions {
interface EventStreamLegacyOptions extends EventStreamOptions {
/**
* When true, also append access_token to the WS URL (legacy fallback).
* Default false — Edge rejects query access_token; prefer
Expand Down
4 changes: 2 additions & 2 deletions app/desktop/src/api/executionTargetQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export interface ExecutionTargetInventorySummary {
byType: Record<ExecutionTargetType, number>;
}

export interface SyncLocalEdgeExecutionTargetInput {
interface SyncLocalEdgeExecutionTargetInput {
deviceId: string;
localEdgeTarget: DesktopExecutionTarget;
registeredTargetId?: string;
Expand Down Expand Up @@ -158,7 +158,7 @@ export function summarizeExecutionTargets(targets: ExecutionTargetInventoryItem[
for (const target of targets) {
byType[String(target.target_type) as ExecutionTargetType] = (byType[String(target.target_type) as ExecutionTargetType] ?? 0) + 1;
if (target.is_online) online += 1;
if (target.health_state === 'healthy') healthy += 1;
if (target.health_state === 'healthy' || target.health_state === 'online') healthy += 1;
else if (target.health_state === 'degraded') degraded += 1;
else if (target.health_state === 'offline') offline += 1;
else unknown += 1;
Expand Down
11 changes: 5 additions & 6 deletions app/desktop/src/api/hubClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ export function createHubClient(opts: HubClientOptions = {}): SharedHubClient {
// exactOptionalPropertyTypes forbids assigning `undefined` to optional string
// fields, so optional top-level fields are only set when defined. The shared
// builder serializes this object verbatim, so the wire shape is unchanged.
const toSharedTarget = (data: CreateExecutionTargetRequest | UpdateExecutionTargetRequest): HubExecutionTargetRequest => {
const toSharedTarget = (data: Partial<HubExecutionTargetRequest>): HubExecutionTargetRequest => {
const target: HubExecutionTargetRequest = {
name: data.name ?? '',
type: (data.target_type as ExecutionTargetType | undefined) ?? 'local_edge',
Expand Down Expand Up @@ -261,9 +261,9 @@ export function createHubClient(opts: HubClientOptions = {}): SharedHubClient {

return {
...client,
createExecutionTarget: (data: CreateExecutionTargetRequest) =>
createExecutionTarget: (data: HubExecutionTargetRequest) =>
client.createExecutionTarget(toSharedTarget(data)),
updateExecutionTarget: (id: string, data: UpdateExecutionTargetRequest) =>
updateExecutionTarget: (id: string, data: Partial<HubExecutionTargetRequest>) =>
client.updateExecutionTarget(id, toSharedTarget(data)),
};
}
Expand All @@ -272,8 +272,8 @@ export function createHubClient(opts: HubClientOptions = {}): SharedHubClient {
// Prefer Hub* / shared types for new code. These keep existing imports compiling
// while execution-target inventory still uses richer desktop fields.

export type ExecutionTargetHealthState = 'unknown' | 'healthy' | 'degraded' | 'offline' | string;
export type ExecutionTargetTrustLevel = 'local' | 'remote' | 'cloud' | 'relay' | string;
export type ExecutionTargetHealthState = 'unknown' | 'online' | 'healthy' | 'degraded' | 'offline' | 'stale' | 'mismatch' | 'registered';
export type ExecutionTargetTrustLevel = 'local' | 'remote' | 'cloud' | 'relay';

export interface CreateExecutionTargetRequest {
name: string;
Expand All @@ -289,7 +289,6 @@ export interface CreateExecutionTargetRequest {
auth_method?: 'none' | 'ssh_tunnel' | 'tailscale_mtls' | 'hub_jwt' | string;
}

export type UpdateExecutionTargetRequest = Partial<CreateExecutionTargetRequest>;

export interface ExecutionTargetListResponse {
items: ExecutionTarget[];
Expand Down
4 changes: 0 additions & 4 deletions app/desktop/src/api/hubQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,6 @@ export function useHubCreateContactGroup() {

// ── Workspace Projects ────────────────────────────────────────────

export interface WorkspaceProjectPage {
items: Awaited<ReturnType<ReturnType<typeof getHubClient>['listWorkspaceProjects']>>['items'];
nextPageCursor?: string;
}

export function useHubWorkspaceProjects(opts?: { enabled?: boolean }) {
return useQuery({
Expand Down
4 changes: 2 additions & 2 deletions app/desktop/src/api/runEvidenceQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { Artifact, Preview, RunDiff } from '@shared/types';
import type { FileDiff } from '@shared/types/chat';
import { fetchArtifacts, fetchPreviews, fetchRunDiff } from './edgeClient';

export interface RunEvidenceState {
interface RunEvidenceState {
diffs: FileDiff[];
artifacts: Artifact[];
previews: Preview[];
Expand Down Expand Up @@ -50,7 +50,7 @@ function fallbackDiff(file: RunDiff['files'][number]): FileDiff {
};
}

export function toReviewDiffs(runDiff: RunDiff | undefined): FileDiff[] {
function toReviewDiffs(runDiff: RunDiff | undefined): FileDiff[] {
if (!runDiff) return [];
return runDiff.files.flatMap((file) => {
const parsed = parseUnifiedDiff(file.diff, file.path) as FileDiff[];
Expand Down
4 changes: 2 additions & 2 deletions app/desktop/src/api/runQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function findActiveEdgeRun(runs: RunInfo[] | undefined): RunInfo | undefi

type RunQuerySnapshot = Array<[readonly unknown[], ListResponse<RunInfo> | undefined]>;

export function snapshotRunQueries(qc: QueryClient): RunQuerySnapshot {
function snapshotRunQueries(qc: QueryClient): RunQuerySnapshot {
return qc.getQueriesData<ListResponse<RunInfo>>({ queryKey: edgeQueryKeys.runs.root });
}

Expand All @@ -50,7 +50,7 @@ export function upsertRunInQueries(qc: QueryClient, run: RunInfo) {
});
}

export function restoreRunsSnapshot(qc: QueryClient, snapshot: RunQuerySnapshot | undefined) {
function restoreRunsSnapshot(qc: QueryClient, snapshot: RunQuerySnapshot | undefined) {
if (!snapshot) return;
for (const [queryKey, value] of snapshot) {
qc.setQueryData(queryKey, value);
Expand Down
12 changes: 6 additions & 6 deletions app/desktop/src/api/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const RunnerSchema = z.object({

// ── Agent ───────────────────────────────────────

export const AgentCapabilitiesSchema = z.object({
const AgentCapabilitiesSchema = z.object({
streaming: z.boolean(),
toolCalls: z.boolean(),
fileChanges: z.boolean(),
Expand Down Expand Up @@ -63,7 +63,7 @@ export const AgentInfoSchema = z.object({

// ── Model catalog ───────────────────────────────

export const ModelCatalogItemSchema = z.object({
const ModelCatalogItemSchema = z.object({
id: z.string(),
value: z.string(),
label: z.string(),
Expand All @@ -79,7 +79,7 @@ export const ModelCatalogItemSchema = z.object({
default: z.boolean().optional(),
});

export const ModelCatalogSourceSchema = z.object({
const ModelCatalogSourceSchema = z.object({
id: z.string(),
label: z.string(),
status: z.enum(['ready', 'configured', 'unavailable']).or(z.string()),
Expand All @@ -93,7 +93,7 @@ export const ModelCatalogResponseSchema = z.object({

// ── Page / List ─────────────────────────────────

export const PageInfoSchema = z.object({
const PageInfoSchema = z.object({
nextCursor: z.string().optional(),
hasMore: z.boolean(),
});
Expand Down Expand Up @@ -162,7 +162,7 @@ export const RunInfoSchema = z.object({
* Parse an API response with a Zod schema. On failure, logs a warning
* and returns the raw data (never throws), so schema drift cannot white-screen the UI.
*/
export const RunDiffFileSchema = z.object({
const RunDiffFileSchema = z.object({
path: z.string(),
diff: z.string(),
status: z.enum(['added', 'modified', 'deleted']),
Expand Down Expand Up @@ -190,7 +190,7 @@ export const ApplyAllRunDiffsResponseSchema = z.object({

// GET /v1/runs/{runId}/checkpoint — pre-run checkpoint inventory (#1968).
// Read-only evidence; no content and no restore/write-back surface.
export const RunCheckpointFileEntrySchema = z.object({
const RunCheckpointFileEntrySchema = z.object({
path: z.string(),
sizeBytes: z.number(),
hash: z.string(),
Expand Down
2 changes: 1 addition & 1 deletion app/desktop/src/components/DesktopChrome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import styles from './DesktopChrome.module.css';

type WindowCommand = 'minimize' | 'toggleMaximize' | 'close';

export interface DesktopChromeProps {
interface DesktopChromeProps {
children: ReactNode;
showNavigationControls?: boolean | undefined;
}
Expand Down
2 changes: 1 addition & 1 deletion app/desktop/src/components/DesktopEntryGate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import agentHubLogo from '@/assets/agenthub-product-icon-rounded.svg';
import tokenDanceLogo from '@/assets/tokendance-product-mark-transparent.svg';
import styles from './DesktopEntryGate.module.css';

export interface DesktopEntryGateProps {
interface DesktopEntryGateProps {
onLoginSuccess: () => void;
onContinueDemo: () => void;
onConnectEdge: () => void;
Expand Down
14 changes: 11 additions & 3 deletions app/desktop/src/components/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,19 @@ export default function LoginForm({ onSuccess }: LoginFormProps) {
await loginWithTokenDance();
setIdentityNotice(t('auth.tokenDanceCallbackPending'));
} catch (err: unknown) {
// #2154 P2-10: never render err.message. The OIDC failures are produced
// in English at the transport layer ("Failed to start OIDC login: fetch
// failed"), so echoing them put a raw technical string in a localized
// login screen. Same mapping as web's LoginForm: known codes resolve
// through auth.error.oidc.<code>, everything else falls back to the
// generic localized failure.
if (err instanceof OidcError) {
setServerError(t(`auth.error.oidc.${err.code}` as const, { detail: err.detail ?? '', defaultValue: 'Login failed' }));
setServerError(t(`auth.error.oidc.${err.code}`, {
detail: err.detail ?? '',
defaultValue: t('auth.error.oidc.default'),
}));
} else {
const message = err instanceof Error ? err.message : '';
setServerError(message || t('auth.error.tokenDanceUnavailable'));
setServerError(t('auth.error.oidc.default'));
}
} finally {
setIdentityLoading(false);
Expand Down
2 changes: 1 addition & 1 deletion app/desktop/src/components/OnboardingOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const ONBOARDING_STEPS: OnboardingStepDescriptor[] = [
{ titleKey: 'onboarding.step2.title', bodyKey: 'onboarding.step2.body' },
];

export interface OnboardingOverlayProps {
interface OnboardingOverlayProps {
/** Called once when the user finishes the last step or skips. */
onFinish: () => void;
}
Expand Down
6 changes: 3 additions & 3 deletions app/desktop/src/hooks/hubIntegrationMappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
parseStringRecord,
} from './hubIntegrationParseHelpers';

export interface TeamRouteContext {
interface TeamRouteContext {
teamId: string;
teamRunId: string;
teamMemberRole?: string;
Expand All @@ -32,7 +32,7 @@ export interface HubDispatchTarget {
deviceId: string;
}

export interface DispatchTargetBindingEvidence {
interface DispatchTargetBindingEvidence {
expectedTargetId: string;
observedTargetId?: string;
expectedEdgeDeviceId: string;
Expand Down Expand Up @@ -332,7 +332,7 @@ export const FINAL_OUTPUT_MAX_CHARS = 32_000;
* - `data`: the unwrapped dispatch payload (parsed from inner `payload` string for relay frames, or the raw frame for direct dispatches)
* - `relayCommandId`: the relay command id when isRelay is true
*/
export interface RelayFrameParseResult {
interface RelayFrameParseResult {
isRelay: boolean;
data: Record<string, unknown>;
relayCommandId: string | null;
Expand Down
Loading
Loading