Skip to content

Commit 60ba5cc

Browse files
fix(agentex-ui): hydrate agent from task on deep-link, drop inaccessible task_id
A URL carrying only task_id (a shared or hand-built link) left the agent pill empty, and a task the caller can't see showed a phantom task view. - ChatView fills agent_name from the task's own (access-checked) agent when the URL omits it, and drops task_id on a 403/404 retrieve instead of leaving a phantom. - agentex-ui-root no longer restores the last-used agent from storage while a task is open, so it can't override the task's agent. Access is unchanged: the BFF attaches credentials server-side and account_id is only a selector, so this can only surface tasks/agents the caller was already authorized to load. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 824286c commit 60ba5cc

2 files changed

Lines changed: 38 additions & 3 deletions

File tree

agentex-ui/components/agentex-ui-root.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,14 @@ export function AgentexUIRoot() {
6868
setLocalAgentName(undefined);
6969
}
7070

71-
if (!agentName && localAgentName) {
71+
// With a task open, its agent wins (hydrated in ChatView) — don't restore from storage.
72+
if (!agentName && !taskID && localAgentName) {
7273
updateParams({ [SearchParamKey.AGENT_NAME]: localAgentName });
7374
}
7475
// eslint-disable-next-line react-hooks/exhaustive-deps
7576
}, [
7677
sgpAccountID,
78+
taskID,
7779
isAgentsFetching,
7880
isAgentByNameFetching,
7981
isAgentByNameError,

agentex-ui/components/primary-content/chat-view.tsx

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { useCallback, useRef } from 'react';
1+
import { useCallback, useEffect, useRef } from 'react';
22

3+
import { APIError } from 'agentex';
34
import { motion } from 'framer-motion';
45

56
import { TaskProvider, useAgentexClient } from '@/components/providers';
@@ -9,6 +10,7 @@ import {
910
SearchParamKey,
1011
useSafeSearchParams,
1112
} from '@/hooks/use-safe-search-params';
13+
import { useTask } from '@/hooks/use-tasks';
1214

1315
import { TaskHeader } from '../task-header/task-header';
1416

@@ -29,10 +31,41 @@ export function ChatView({
2931
}: ChatViewProps) {
3032
const { agentexClient } = useAgentexClient();
3133
const { data: agents = [] } = useAgents(agentexClient);
32-
const { updateParams } = useSafeSearchParams();
34+
const { agentName, updateParams } = useSafeSearchParams();
35+
const {
36+
data: task,
37+
isError: isTaskError,
38+
error: taskError,
39+
} = useTask({ agentexClient, taskId: taskID });
3340

3441
const headerRef = useRef<HTMLDivElement>(null);
3542

43+
// Deep links may carry only task_id — fill the agent pill from the task's own agent.
44+
const taskAgentName = task?.agents?.[0]?.name;
45+
useEffect(() => {
46+
if (agentName || !taskAgentName) return;
47+
updateParams({ [SearchParamKey.AGENT_NAME]: taskAgentName }, true);
48+
}, [agentName, taskAgentName, updateParams]);
49+
50+
// Drop a task_id the client can't use: a 4xx means it's bad/off-limits (400/403/404/422).
51+
// 401 is handled by the session refresh + login redirect, and 429/5xx are transient.
52+
useEffect(() => {
53+
const status = taskError instanceof APIError ? taskError.status : undefined;
54+
if (
55+
isTaskError &&
56+
status !== undefined &&
57+
status >= 400 &&
58+
status < 500 &&
59+
status !== 401 &&
60+
status !== 429
61+
) {
62+
updateParams(
63+
{ [SearchParamKey.TASK_ID]: null, [SearchParamKey.AGENT_NAME]: null },
64+
true
65+
);
66+
}
67+
}, [isTaskError, taskError, updateParams]);
68+
3669
const handleSelectAgent = useCallback(
3770
(agentName: string | undefined) => {
3871
updateParams({

0 commit comments

Comments
 (0)