From 739c1f10c6037de2e09322059f11e083230861a4 Mon Sep 17 00:00:00 2001 From: Vikas Singhal Date: Fri, 24 Jul 2026 19:11:34 +0530 Subject: [PATCH 1/2] feat(tasks web): deep-link the room tab in the URL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hash detail becomes / (discussion|description|session), so refreshing or sharing a task URL lands on the right tab. Tabs are real links (openTaskTab → nav), the active tab derives from the URL (no local state), and a /session link with no session falls back to discussion. --- web/src/App.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) 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 &&
}
From 824fc7041238fa3fc6e75f427d9ed4c5b08f3d19 Mon Sep 17 00:00:00 2001 From: Vikas Singhal Date: Fri, 24 Jul 2026 19:12:07 +0530 Subject: [PATCH 2/2] =?UTF-8?q?chore:=20v0.264.2=20=E2=80=94=20deep-link?= =?UTF-8?q?=20room=20tabs=20(CHANGELOG)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) 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",