diff --git a/packages/app/package.json b/packages/app/package.json index 625b3083..3d4e8646 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@deepagent-code/app", - "version": "1.4.2", + "version": "1.4.3", "description": "", "type": "module", "exports": { diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index 1568b3df..0da7afc6 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -9,7 +9,7 @@ import { Font } from "@deepagent-code/ui/font" import { Splash } from "@deepagent-code/ui/logo" import { ThemeProvider } from "@deepagent-code/ui/theme/context" import { MetaProvider } from "@solidjs/meta" -import { type BaseRouterProps, Navigate, Route, Router } from "@solidjs/router" +import { type BaseRouterProps, Navigate, Route, Router, useLocation, useNavigate } from "@solidjs/router" import { QueryClient, QueryClientProvider } from "@tanstack/solid-query" import { Effect } from "effect" import { @@ -45,7 +45,7 @@ import { PromptProvider } from "@/context/prompt" import { ServerConnection, ServerProvider, serverName, useServer } from "@/context/server" import { SettingsProvider } from "@/context/settings" import { TerminalProvider } from "@/context/terminal" -import { TabsProvider } from "@/context/tabs" +import { startupTab, TabsProvider, useTabs } from "@/context/tabs" import { WslServersProvider } from "@/wsl/context" import DirectoryLayout from "@/pages/directory-layout" import Layout from "@/pages/layout" @@ -143,6 +143,25 @@ function SessionProviders(props: ParentProps) { } function RouterRoot(props: ParentProps<{ appChildren?: JSX.Element }>) { + const tabs = useTabs() + const server = useServer() + const navigate = useNavigate() + const location = useLocation() + + // Restore the explicitly persisted active tab. A cross-server directory is never + // navigated until the target server has become active. + createEffect(() => { + if (!tabs.ready()) return + if (location.pathname !== "/") return + const tab = startupTab(tabs.store, tabs.active.key, server.list) + if (!tab) return + if (server.key !== tab.server) { + server.setActive(tab.server) + return + } + navigate(`/${tab.dirBase64}/session`, { replace: true }) + }) + return ( {/*}>*/} diff --git a/packages/app/src/components/deepagent/oversight-dashboard.tsx b/packages/app/src/components/deepagent/oversight-dashboard.tsx index 58ec0cd5..09f72cd3 100644 --- a/packages/app/src/components/deepagent/oversight-dashboard.tsx +++ b/packages/app/src/components/deepagent/oversight-dashboard.tsx @@ -55,7 +55,16 @@ function MetricCard(props: { label: string; value: string; tone?: "ok" | "warn" ) } -export const OversightDashboard: Component = () => { +// Phase 2: OversightDashboard now accepts a selected subagent session ID so takeover/rollback +// automatically target the chosen session, and trace nodes can reverse-select a subagent. +export type OversightDashboardProps = { + /** When provided, takeover and rollback default to this session ID. */ + selectedSessionID?: string + /** Called when a trace node with a sessionID is clicked so the caller can select that subagent. */ + onSessionSelect?: (sessionID: string) => void +} + +export const OversightDashboard: Component = (props) => { const sdk = useSDK() const language = useLanguage() const client = () => sdk.client as unknown as OversightClient @@ -118,7 +127,9 @@ export const OversightDashboard: Component = () => { if (!reason) return setTakeoverBusy(true) setTakeoverNote(null) - const result = await recordHumanTakeover(client(), { reason }) + // Phase 2: automatically pass the selected session ID when present. + const sessionID = props.selectedSessionID + const result = await recordHumanTakeover(client(), { reason, ...(sessionID ? { sessionID } : {}) }) setTakeoverBusy(false) if (result.ok) { setTakeoverReason("") @@ -133,13 +144,17 @@ export const OversightDashboard: Component = () => { } // ── §D2 rollback (P4.4) ───────────────────────────────────────────────────────── - const [rollbackSession, setRollbackSession] = createSignal("") + // Phase 2: rollback input is pre-seeded with the selected session ID. Users may override it. const [rollbackReason, setRollbackReason] = createSignal("") const [rollbackBusy, setRollbackBusy] = createSignal(false) const [rollbackNote, setRollbackNote] = createSignal(null) + // The effective session ID for rollback: the input field overrides; falls back to selectedSessionID. + const [rollbackSessionOverride, setRollbackSessionOverride] = createSignal("") + const effectiveRollbackSession = () => rollbackSessionOverride() || props.selectedSessionID || "" + const submitRollback = async () => { - const sessionID = rollbackSession().trim() + const sessionID = effectiveRollbackSession().trim() if (!sessionID) return const reason = rollbackReason().trim() setRollbackBusy(true) @@ -147,7 +162,7 @@ export const OversightDashboard: Component = () => { const result = await recordRollback(client(), { sessionID, ...(reason ? { reason } : {}) }) setRollbackBusy(false) if (result.ok) { - setRollbackSession("") + setRollbackSessionOverride("") setRollbackReason("") setRollbackNote( result.record?.outcome === "noop" @@ -328,6 +343,17 @@ export const OversightDashboard: Component = () => {
{fmtTime(node.createdAt)}
+ {/* Phase 2: if this node is tied to a specific subagent session, offer + a reverse-select link so the user can jump to that subagent row. */} + + + )} @@ -372,11 +398,13 @@ export const OversightDashboard: Component = () => {

{language.t("oversight.rollback.title")}

{language.t("oversight.rollback.description")}

+ {/* Phase 2: when a subagent is selected the placeholder is replaced by the session ID. + Typing in this field overrides the pre-selected session. */} setRollbackSession(e.currentTarget.value)} + placeholder={props.selectedSessionID || language.t("oversight.rollback.sessionPlaceholder")} + value={rollbackSessionOverride()} + onInput={(e) => setRollbackSessionOverride(e.currentTarget.value)} />