diff --git a/CHANGELOG.md b/CHANGELOG.md index e4ded06..e197857 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ new version heading in the same commit. ## [Unreleased] +## [0.264.2] — 2026-07-24 +### Changed +- **Task room tabs are deep-linked in the URL.** The hash detail becomes `/` + (`discussion` | `description` | `session`), so refreshing or sharing a task URL lands on the right tab. + The tabs are real links and the active tab derives from the URL (a `/session` link on a task with no + session falls back to Discussion). + ## [0.264.1] — 2026-07-24 ### Fixed - **The embedded Session terminal now fills the Session tab.** `TerminalFrame` is `flex-1`/`min-h-0` and diff --git a/package-lock.json b/package-lock.json index b0a47e1..a135571 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "agent-os", - "version": "0.264.1", + "version": "0.264.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "agent-os", - "version": "0.264.1", + "version": "0.264.2", "license": "MIT", "bin": { "agent-os": "bin/agent-os" diff --git a/package.json b/package.json index 2acae9b..0d51610 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "agent-os", - "version": "0.264.1", + "version": "0.264.2", "description": "A generic, governed operating system for running autonomous agents safely across brands. Ships with a local web console.", "license": "MIT", "type": "commonjs", diff --git a/web/src/App.tsx b/web/src/App.tsx index 0cd0a3f..f461cee 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -7495,11 +7495,15 @@ function TasksPage({ me, agents, taskId, onOpen, nav }: { me: Member; agents: Ag const [q, setQ] = useState('') // Selection is URL-driven (#/tasks/) so a task detail is a shareable permalink — pasting it opens // the modal automatically. Opening a card just navigates; closing clears the detail segment. - const selId = taskId || null + // The hash detail is `` or `/` (tab ∈ discussion|description|session) — so the open + // task AND its room tab are deep-linked (refresh/share lands on the right tab). + const [routeTaskId, routeTab] = (taskId || '').split('/') + const selId = routeTaskId || null + const roomTab: 'discussion' | 'description' | 'session' = routeTab === 'description' || routeTab === 'session' ? routeTab : 'discussion' const openTask = (id: string) => nav('tasks', id) + const openTaskTab = (id: string, tab: 'discussion' | 'description' | 'session') => nav('tasks', tab === 'discussion' ? id : `${id}/${tab}`) const closeTask = () => { setEditing(false); nav('tasks') } const [detail, setDetail] = useState<{ task: Task; events: TaskEvent[]; attachments: TaskAttachment[]; dependents: string[]; discussion: TaskTimelineEntry[]; unread: number } | null>(null) - const [roomTab, setRoomTab] = useState<'discussion' | 'description' | 'session'>('discussion') const [busy, setBusy] = useState(false) const [hint, setHint] = useState('') // view + filters @@ -7611,7 +7615,7 @@ function TasksPage({ me, agents, taskId, onOpen, nav }: { me: Member; agents: Ag if (editing) return // don't overwrite an in-progress edit on a background refresh api.task(selId).then((r) => { if (r.task) setDetail({ task: r.task, events: r.events ?? [], attachments: r.attachments ?? [], dependents: r.dependents ?? [], discussion: r.discussion ?? [], unread: r.unread ?? 0 }) }) }, [selId, tasks, editing]) - useEffect(() => { setEditing(false); setConfirmDel(false); setRoomTab('discussion') }, [selId]) // fresh drawer per selection + useEffect(() => { setEditing(false); setConfirmDel(false) }, [selId]) // fresh drawer per selection // Client-side filtering over the (≤500) board — cheap, and keeps the lens shareable via UI state. const labelsPresent = [...new Set((tasks ?? []).flatMap((t) => t.labels))].sort() @@ -7921,8 +7925,9 @@ function TasksPage({ me, agents, taskId, onOpen, nav }: { me: Member; agents: Ag const parts = discussions[t.id]?.participants ?? [] const live = liveOf(t) const sessTmux = live?.tmux || (t.lastSessionId ? 'aos-' + t.lastSessionId : '') + const activeTab: 'discussion' | 'description' | 'session' = roomTab === 'session' && !sessTmux ? 'discussion' : roomTab const roomTab_btn = (id: 'discussion' | 'description' | 'session', label: ReactNode) => ( - + openTaskTab(t.id, id))} className={`-mb-px flex items-center gap-1.5 border-b-2 px-3 py-2 text-[13px] font-medium no-underline transition-colors ${activeTab === id ? 'border-primary text-foreground' : 'border-transparent text-muted-foreground hover:text-foreground'}`}>{label} ) return (
@@ -7944,15 +7949,15 @@ function TasksPage({ me, agents, taskId, onOpen, nav }: { me: Member; agents: Ag {sessTmux && roomTab_btn('session', <>Session{live && })}
- {roomTab === 'discussion' &&
refreshDetail(t.id)} />
} - {roomTab === 'description' && ( + {activeTab === 'discussion' &&
refreshDetail(t.id)} />
} + {activeTab === 'description' && (
{t.body ?
{t.body}
:
No description.
}
)} - {roomTab === 'session' && sessTmux &&
} + {activeTab === 'session' && sessTmux &&
}